Repository: opentensor/bittensor (language: python)

Issue:
Cache get_decoder_class
Currently, a large portion of time (29000+ calls for get_delegates) for RPC calls is taken up by decoding. Profiling this shows that the vast majority of this decoding time is actually caused by calls to the `scalecodec.base.RuntimeConfiguration.get_decoder_class` method. 

Because of the fairly limited number of decoder classes, we should be able to cache this with `functools.cache` to see large speed improvements.


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index d1b95df6c5..6bd5e47978 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -278,9 +278,6 @@ jobs:
 workflows:
   compatibility_checks:
     jobs:
-      - check_compatibility:
-          python_version: "3.8"
-          name: check-compatibility-3.8
       - check_compatibility:
           python_version: "3.9"
           name: check-compatibility-3.9
@@ -294,7 +291,7 @@ workflows:
   pr-requirements:
     jobs:
       - black:
-          python-version: "3.8.12"
+          python-version: "3.9.13"
       - build-and-test:
           matrix:
             parameters:
@@ -305,7 +302,7 @@ workflows:
       - lint-and-type-check:
           matrix:
             parameters:
-              python-version: ["3.8.12", "3.9.13", "3.10.6"]
+              python-version: ["3.9.13", "3.10.6", "3.11.4"]
           requires:
             - build-and-test
       #- coveralls:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9123de2c44..4e92566cef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,60 @@
 # Changelog
 
+## 7.0.0 / 2024-05-29
+
+## What's Changed
+* replace torch with numpy by @andreea-popescu-reef in https://github.com/opentensor/bittensor/pull/1777
+* Fix broken link in contrib/RELEASE_GUIDELINES #1821 by @thewhaleking in https://github.com/opentensor/bittensor/pull/1823
+* Tests: Added coverage for set_weights by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1825
+* Remove irrelevant call to get_delegates method. by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1826
+* Support for string mnemonic thru cli when regenerating coldkeys by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1815
+* Logging: Added _primary_loggers by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1797
+* Add in check for minimum stake for unstaking by @thewhaleking in https://github.com/opentensor/bittensor/pull/1832
+* Cache get_decoder_class by @thewhaleking in https://github.com/opentensor/bittensor/pull/1834
+* Warmfix/change decoder cacheing by @thewhaleking in https://github.com/opentensor/bittensor/pull/1842
+* Fix typo in warmfix by @thewhaleking in https://github.com/opentensor/bittensor/pull/1844
+* Add the command btcli root list_delegates_lite to handle the Delegate… by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1840
+* Change: console.error => console.print by @thewhaleking in https://github.com/opentensor/bittensor/pull/1849
+* Small fix with receiving delegates based on a 4-hour archive block by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1854
+* Replace torch with numpy by @sepehr-opentensor in https://github.com/opentensor/bittensor/pull/1786
+* Versioning: Enforcement for eth-utils by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1852
+* Versioning: Dependencies for FastAPI for Apple M's by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1855
+* Retrieving error types from the metadata of the Substrate palette SubtensorModule for the btcli console (logic) by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1862
+* Add version check caching, fix version comparison by @olzhasar-reef in https://github.com/opentensor/bittensor/pull/1835
+* Tests: Added coverage for root.py by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1877
+* Tests: Added coverage for network.py by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1879
+* Tests: extends coverage for overview cmd part 1 by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1873
+* Tests: Added coverage for Unstaking by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1878
+* Tests: Added coverage for staking by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1837
+* Tests: Added coverage for Delegation by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1874
+* Updated error message and a test typo. by @thewhaleking in https://github.com/opentensor/bittensor/pull/1871
+* fix: deprecated usage of `Balances::transfer` method by @orriin in https://github.com/opentensor/bittensor/pull/1886
+* Fix Type Annotation by @opendansor in https://github.com/opentensor/bittensor/pull/1895
+* Docstrings updates for list delegate lite feature by @rajkaramchedu in https://github.com/opentensor/bittensor/pull/1890
+* Add Pre-commit Checker in scripts. Helps reduce CI calls. by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1893
+* fix get_coldkey_password_from_environment resolving wrong password by @mjurbanski-reef in https://github.com/opentensor/bittensor/pull/1843
+* Drop python 3.8 support by @mjurbanski-reef in https://github.com/opentensor/bittensor/pull/1892
+* feat: Refactor phase 2 overview cmd & add test cov. Adds factories by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1887
+* Add setting delegate take by @gztensor in https://github.com/opentensor/bittensor/pull/1903
+* E2E Test Patterns by @orriin in https://github.com/opentensor/bittensor/pull/1885
+* chore: correct method types by @distributedstatemachine in https://github.com/opentensor/bittensor/pull/1907
+* bittensor.btlogging refactoring by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1896
+* Part 1 for refactoring bittensor/subtensor.py by @RomanCh-OT in https://github.com/opentensor/bittensor/pull/1911
+* Update: Pydantic V2 by @opendansor in https://github.com/opentensor/bittensor/pull/1889
+* Add back compatibility with torch by @thewhaleking in https://github.com/opentensor/bittensor/pull/1904
+* Release/6.12.2 by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1910
+* Chore: Updated dev requirements by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/1946
+
+## New Contributors
+* @andreea-popescu-reef made their first contribution in https://github.com/opentensor/bittensor/pull/1777
+* @thewhaleking made their first contribution in https://github.com/opentensor/bittensor/pull/1823
+* @RomanCh-OT made their first contribution in https://github.com/opentensor/bittensor/pull/1826
+* @olzhasar-reef made their first contribution in https://github.com/opentensor/bittensor/pull/1835
+* @orriin made their first contribution in https://github.com/opentensor/bittensor/pull/1886
+* @opendansor made their first contribution in https://github.com/opentensor/bittensor/pull/1895
+
+**Full Changelog**: https://github.com/opentensor/bittensor/compare/v6.12.2...v7.0.0
+
 ## 6.12.2 / 2024-05-20
 
 ## What's Changed
@@ -70,8 +125,6 @@
 
 **Full Changelog**: https://github.com/opentensor/bittensor/compare/v6.9.3...v6.10.0
 
-
-
 ## 6.9.3 / 2024-03-12
 
 ## What's Changed
@@ -111,8 +164,8 @@
 * Expands type checking to subtensor by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1731
 * Feature: Synapse passing type check by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1725
 * bump req for security vulnerability in crpytography by @ifrit98 in https://github.com/opentensor/bittensor/pull/1718
-* Fix: proper association with wallet dir and coldkey addr #1739 by @gus-opentensor & @sepehr-opentensor 
-* Fixed event lookup on new network added #1741 by @shibshib 
+* Fix: proper association with wallet dir and coldkey addr #1739 by @gus-opentensor & @sepehr-opentensor
+* Fixed event lookup on new network added #1741 by @shibshib
 
 **Full Changelog**: https://github.com/opentensor/bittensor/compare/v6.8.2...v6.9.0
 
@@ -144,11 +197,11 @@
 * logging off switch by @ifrit98 in https://github.com/opentensor/bittensor/pull/1704
 * Extrinsic update by @ifrit98 in https://github.com/opentensor/bittensor/pull/1703
 * Bittensor shared request layer by @ifrit98 in https://github.com/opentensor/bittensor/pull/1698
-* Add no_prompt argument to help printout in https://github.com/opentensor/bittensor/pull/1707 
-* Adds mypi typechecking to circleci by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1705 
+* Add no_prompt argument to help printout in https://github.com/opentensor/bittensor/pull/1707
+* Adds mypi typechecking to circleci by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1705
 * Remove set weights ttl now that we have a better extrinsic method by @ifrit98
-* Bug fix in overview command for dereg stake with outdated `stake_info` object fields by @ifrit98 in https://github.com/opentensor/bittensor/pull/1712 
-* Moves mock wallet creation to temp dir by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1711 
+* Bug fix in overview command for dereg stake with outdated `stake_info` object fields by @ifrit98 in https://github.com/opentensor/bittensor/pull/1712
+* Moves mock wallet creation to temp dir by @gus-opentensor in https://github.com/opentensor/bittensor/pull/1711
 
 
 **Full Changelog**: https://github.com/opentensor/bittensor/compare/v6.7.2...v6.8.0
@@ -711,7 +764,7 @@
   - allow set synapse All using flag
   - add test
   - use dot get
-  
+
 - [Feature] Mark registration threads as daemons (https://github.com/opentensor/bittensor/pull/998)
   - make solver processes daemons
 
@@ -761,9 +814,9 @@
 ## 3.4.0 / 2022-10-13
 
 ## What's Changed
-* Parameters update by @Eugene-hu  #936 
-* Bittensor Generate by @unconst  #941 
-* Prometheus by @unconst  #928 
+* Parameters update by @Eugene-hu  #936
+* Bittensor Generate by @unconst  #941
+* Prometheus by @unconst  #928
 * [Tooling][Release] Adding release script by @eduardogr in https://github.com/opentensor/bittensor/pull/948
 
 
@@ -822,7 +875,7 @@
 ## 3.3.0 / 2022-08-16
 
 ### CUDA registration
-This release adds the ability to complete the registration using a CUDA-capable device.   
+This release adds the ability to complete the registration using a CUDA-capable device.
 See https://github.com/opentensor/cubit/releases/tag/v1.0.5 for the required `cubit` v1.0.5 release
 
 Also a few bug fixes for the CLI
@@ -855,5 +908,5 @@ This release refactors the registration code for CPU registration to improve sol
 
 ### Synapse update
 
-## 
+##
 
diff --git a/Dockerfile b/Dockerfile
index 15081dbe70..5a355b5cf6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 # syntax=docker/dockerfile:1
-FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-devel
+FROM python:3.11.8-bookworm
 
 LABEL bittensor.image.authors="bittensor.com" \
 	bittensor.image.vendor="Bittensor" \
@@ -9,19 +9,13 @@ LABEL bittensor.image.authors="bittensor.com" \
 	bittensor.image.revision="${VCS_REF}" \
 	bittensor.image.created="${BUILD_DATE}" \
 	bittensor.image.documentation="https://app.gitbook.com/@opentensor/s/bittensor/"
-LABEL bittensor.dependencies.versions.torch="2.0.1"
-LABEL bittensor.dependencies.versions.cuda="11.7"
 ARG DEBIAN_FRONTEND=noninteractive
 
-#nvidia key migration
-RUN apt-key del 7fa2af80
-RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub
-RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu2004/x86_64/7fa2af80.pub
 # Update the base image
 RUN apt update && apt upgrade -y
 # Install bittensor
 ## Install dependencies
-RUN apt install -y curl sudo nano git htop netcat wget unzip tmux apt-utils cmake build-essential
+RUN apt install -y curl sudo nano git htop netcat-openbsd wget unzip tmux apt-utils cmake build-essential
 ## Upgrade pip
 RUN pip3 install --upgrade pip
 
diff --git a/README.md b/README.md
index a431f5e8f5..b9284f2a5b 100644
--- a/README.md
+++ b/README.md
@@ -221,6 +221,11 @@ source ~/.bashrc  # Reload Bash configuration to take effect
 # The Bittensor Package
 The bittensor package contains data structures for interacting with the bittensor ecosystem, writing miners, validators and querying the network. Additionally, it provides many utilities for efficient serialization of Tensors over the wire, performing data analysis of the network, and other useful utilities.
 
+In the 7.0.0 release, we have removed `torch` by default. However, you can still use `torch` by setting the environment variable
+`USE_TORCH=1` and making sure that you have installed the `torch` library. 
+You can install `torch` by running `pip install bittensor[torch]` (if installing via PyPI), or by running `pip install -e ".[torch]"` (if installing from source). 
+We will not be adding any new functionality based on torch.
+
 Wallet: Interface over locally stored bittensor hot + coldkey styled wallets. 
 ```python
 import bittensor
@@ -273,7 +278,7 @@ Synapse: Responsible for defining the protocol definition between axon servers a
 ```python
 class Topk( bittensor.Synapse ):
     topk: int = 2  # Number of "top" elements to select
-    input: bittensor.Tensor = pydantic.Field(..., allow_mutation=False)  # Ensure that input cannot be set on the server side. 
+    input: bittensor.Tensor = pydantic.Field(..., frozen=True)  # Ensure that input cannot be set on the server side. 
     v: bittensor.Tensor = None
     i: bittensor.Tensor = None
 
@@ -329,8 +334,8 @@ my_axon.attach(
 ).start()
 ```     
 
-Dendrite: Inheriting from PyTorch's Module class, represents the abstracted implementation of a network client module designed 
-to send requests to those endpoints to receive inputs.
+Dendrite: Represents the abstracted implementation of a network client module
+designed to send requests to those endpoints to receive inputs.
 
 Example:
 ```python
diff --git a/VERSION b/VERSION
index cf1279ccf0..66ce77b7ea 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-6.12.2
\ No newline at end of file
+7.0.0
diff --git a/bittensor/__init__.py b/bittensor/__init__.py
index 8678197e0b..b8700daf79 100644
--- a/bittensor/__init__.py
+++ b/bittensor/__init__.py
@@ -27,8 +27,7 @@
 nest_asyncio.apply()
 
 # Bittensor code and protocol version.
-
-__version__ = "6.12.2"
+__version__ = "7.0.0"
 
 version_split = __version__.split(".")
 __version_as_int__: int = (
@@ -286,6 +285,8 @@ def debug(on: bool = True):
     ProposalCallData,
     ProposalVoteData,
 )
+
+from . import subtensor as subtensor_module
 from .subtensor import subtensor as subtensor
 from .cli import cli as cli, COMMANDS as ALL_COMMANDS
 from .btlogging import logging
diff --git a/bittensor/axon.py b/bittensor/axon.py
index 1301b32c8d..52e6db3a30 100644
--- a/bittensor/axon.py
+++ b/bittensor/axon.py
@@ -20,29 +20,29 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-import os
-import uuid
+import argparse
+import asyncio
+import contextlib
 import copy
+import inspect
 import json
+import os
+import threading
 import time
-import asyncio
-import inspect
-import uvicorn
-import argparse
 import traceback
-import threading
-import bittensor
-import contextlib
-
+import uuid
 from inspect import signature, Signature, Parameter
+from typing import List, Optional, Tuple, Callable, Any, Dict
+
+import uvicorn
+from fastapi import FastAPI, APIRouter, Depends
 from fastapi.responses import JSONResponse
-from substrateinterface import Keypair
-from fastapi import FastAPI, APIRouter, Request, Response, Depends
-from starlette.responses import Response
-from starlette.requests import Request
 from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
-from typing import List, Optional, Tuple, Callable, Any, Dict
+from starlette.requests import Request
+from starlette.responses import Response
+from substrateinterface import Keypair
 
+import bittensor
 from bittensor.errors import (
     InvalidRequestNameError,
     SynapseDendriteNoneException,
@@ -567,7 +567,7 @@ def verify_custom(synapse: MyCustomSynapse):
         self.forward_fns[request_name] = forward_fn
 
         # Parse required hash fields from the forward function protocol defaults
-        required_hash_fields = request_class.__dict__["__fields__"][
+        required_hash_fields = request_class.__dict__["model_fields"][
             "required_hash_fields"
         ].default
         self.required_hash_fields[request_name] = required_hash_fields
diff --git a/bittensor/btlogging/__init__.py b/bittensor/btlogging/__init__.py
index 16dc150cf0..6bf6d2bf35 100644
--- a/bittensor/btlogging/__init__.py
+++ b/bittensor/btlogging/__init__.py
@@ -1,7 +1,3 @@
-""" 
-Standardized logging for Bittensor.
-"""
-
 # The MIT License (MIT)
 # Copyright © 2021 Yuma Rao
 
@@ -19,10 +15,13 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-import os
-import argparse
+"""
+btlogging sub-package standardized logging for Bittensor.
+
+This module provides logging functionality for the Bittensor package. It includes custom loggers, handlers, and
+formatters to ensure consistent logging throughout the project.
+"""
 
-import bittensor.config
 from bittensor.btlogging.loggingmachine import LoggingMachine
 
 
diff --git a/bittensor/btlogging/defines.py b/bittensor/btlogging/defines.py
index 605ff94df4..c87177ffd0 100644
--- a/bittensor/btlogging/defines.py
+++ b/bittensor/btlogging/defines.py
@@ -1,3 +1,22 @@
+# The MIT License (MIT)
+# Copyright © 2023 OpenTensor Foundation
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Btlogging constant definition module."""
+
 BASE_LOG_FORMAT = "%(asctime)s | %(levelname)s | %(message)s"
 TRACE_LOG_FORMAT = (
     f"%(asctime)s | %(levelname)s | %(name)s:%(filename)s:%(lineno)s | %(message)s"
diff --git a/bittensor/btlogging/format.py b/bittensor/btlogging/format.py
index 64a4ca45b6..5f2c8cb866 100644
--- a/bittensor/btlogging/format.py
+++ b/bittensor/btlogging/format.py
@@ -1,40 +1,65 @@
-import time
+# The MIT License (MIT)
+# Copyright © 2023 OpenTensor Foundation
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""
+btlogging.format module
+
+This module defines custom logging formatters for the Bittensor project.
+"""
+
 import logging
+import time
+from typing import Dict
+
 from colorama import init, Fore, Back, Style
 
 
 init(autoreset=True)
 
-TRACE_LEVEL_NUM = 5
-SUCCESS_LEVEL_NUM = 21
+TRACE_LEVEL_NUM: int = 5
+SUCCESS_LEVEL_NUM: int = 21
 
 
-def trace(self, message, *args, **kws):
+def _trace(self, message: str, *args, **kws):
     if self.isEnabledFor(TRACE_LEVEL_NUM):
         self._log(TRACE_LEVEL_NUM, message, args, **kws)
 
 
-def success(self, message, *args, **kws):
+def _success(self, message: str, *args, **kws):
     if self.isEnabledFor(SUCCESS_LEVEL_NUM):
         self._log(SUCCESS_LEVEL_NUM, message, args, **kws)
 
 
 logging.SUCCESS = SUCCESS_LEVEL_NUM
 logging.addLevelName(SUCCESS_LEVEL_NUM, "SUCCESS")
-logging.Logger.success = success
+logging.Logger.success = _success
 
 logging.TRACE = TRACE_LEVEL_NUM
 logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
-logging.Logger.trace = trace
+logging.Logger.trace = _trace
 
-emoji_map = {
+emoji_map: Dict[str, str] = {
     ":white_heavy_check_mark:": "✅",
     ":cross_mark:": "❌",
     ":satellite:": "🛰️",
 }
 
 
-color_map = {
+color_map: Dict[str, str] = {
     "<red>": Fore.RED,
     "</red>": Style.RESET_ALL,
     "<blue>": Fore.BLUE,
@@ -44,7 +69,7 @@ def success(self, message, *args, **kws):
 }
 
 
-log_level_color_prefix = {
+log_level_color_prefix: Dict[int, str] = {
     logging.NOTSET: Fore.RESET,
     logging.TRACE: Fore.MAGENTA,
     logging.DEBUG: Fore.BLUE,
@@ -56,12 +81,12 @@ def success(self, message, *args, **kws):
 }
 
 
-LOG_FORMATS = {
+LOG_FORMATS: Dict[int, str] = {
     level: f"{Fore.BLUE}%(asctime)s{Fore.RESET} | {Style.BRIGHT}{color}%(levelname)s\033[0m | %(message)s"
     for level, color in log_level_color_prefix.items()
 }
 
-LOG_TRACE_FORMATS = {
+LOG_TRACE_FORMATS: Dict[int, str] = {
     level: f"{Fore.BLUE}%(asctime)s{Fore.RESET}"
     f" | {Style.BRIGHT}{color}%(levelname)s{Fore.RESET}{Back.RESET}{Style.RESET_ALL}"
     f" | %(name)s:%(filename)s:%(lineno)s"
@@ -69,16 +94,41 @@ def success(self, message, *args, **kws):
     for level, color in log_level_color_prefix.items()
 }
 
+DEFAULT_LOG_FORMAT: str = (
+    f"{Fore.BLUE}%(asctime)s{Fore.RESET} | "
+    f"{Style.BRIGHT}{Fore.WHITE}%(levelname)s{Style.RESET_ALL} | "
+    f"%(name)s:%(filename)s:%(lineno)s | %(message)s"
+)
+
+DEFAULT_TRACE_FORMAT: str = (
+    f"{Fore.BLUE}%(asctime)s{Fore.RESET} | "
+    f"{Style.BRIGHT}{Fore.WHITE}%(levelname)s{Style.RESET_ALL} | "
+    f"%(name)s:%(filename)s:%(lineno)s | %(message)s"
+)
+
 
 class BtStreamFormatter(logging.Formatter):
+    """
+    A custom logging formatter for the Bittensor project that overrides the time formatting to include milliseconds,
+    centers the level name, and applies custom log formats, emojis, and colors.
+    """
+
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.trace = False
 
-    def formatTime(self, record, datefmt=None):
+    def formatTime(self, record, datefmt=None) -> str:
         """
         Override formatTime to add milliseconds.
+
+        Args:
+            record (logging.LogRecord): The log record.
+            datefmt (str, optional): The date format string.
+
+        Returns:
+            s (str): The formatted time string with milliseconds.
         """
+
         created = self.converter(record.created)
         if datefmt:
             s = time.strftime(datefmt, created)
@@ -87,16 +137,32 @@ def formatTime(self, record, datefmt=None):
         s += ".{:03d}".format(int(record.msecs))
         return s
 
-    def format(self, record):
-        # Save the original format configured by the user
-        # when the logger formatter was instantiated
+    def format(self, record) -> str:
+        """
+        Override format to apply custom formatting including emojis and colors.
+
+        This method saves the original format, applies custom formatting based on the log level and trace flag, replaces
+        text with emojis and colors, and then returns the formatted log record.
+
+        Args:
+            record (logging.LogRecord): The log record.
+
+        Returns:
+            result (str): The formatted log record.
+        """
+
         format_orig = self._style._fmt
         record.levelname = f"{record.levelname:^16}"
 
-        if self.trace is True:
-            self._style._fmt = LOG_TRACE_FORMATS[record.levelno]
+        if record.levelno not in LOG_FORMATS:
+            self._style._fmt = (
+                DEFAULT_TRACE_FORMAT if self.trace else DEFAULT_LOG_FORMAT
+            )
         else:
-            self._style._fmt = LOG_FORMATS[record.levelno]
+            if self.trace is True:
+                self._style._fmt = LOG_TRACE_FORMATS[record.levelno]
+            else:
+                self._style._fmt = LOG_FORMATS[record.levelno]
 
         for text, emoji in emoji_map.items():
             record.msg = record.msg.replace(text, emoji)
@@ -110,14 +176,30 @@ def format(self, record):
         return result
 
     def set_trace(self, state: bool = True):
+        """Change formatter state."""
         self.trace = state
 
 
 class BtFileFormatter(logging.Formatter):
-    def formatTime(self, record, datefmt=None):
+    """
+    BtFileFormatter
+
+    A custom logging formatter for the Bittensor project that overrides the time formatting to include milliseconds and
+    centers the level name.
+    """
+
+    def formatTime(self, record, datefmt=None) -> str:
         """
         Override formatTime to add milliseconds.
+
+        Args:
+            record (logging.LogRecord): The log record.
+            datefmt (str, optional): The date format string.
+
+        Returns:
+            s (str): The formatted time string with milliseconds.
         """
+
         created = self.converter(record.created)
         if datefmt:
             s = time.strftime(datefmt, created)
@@ -126,6 +208,15 @@ def formatTime(self, record, datefmt=None):
         s += ".{:03d}".format(int(record.msecs))
         return s
 
-    def format(self, record):
+    def format(self, record) -> str:
+        """
+        Override format to center the level name.
+
+        Args:
+            record (logging.LogRecord): The log record.
+
+        Returns:
+            formated record (str): The formatted log record.
+        """
         record.levelname = f"{record.levelname:^16}"
         return super().format(record)
diff --git a/bittensor/btlogging/helpers.py b/bittensor/btlogging/helpers.py
index e4d5b0492c..532c1f7166 100644
--- a/bittensor/btlogging/helpers.py
+++ b/bittensor/btlogging/helpers.py
@@ -1,9 +1,36 @@
+# The MIT License (MIT)
+# Copyright © 2023 OpenTensor Foundation
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""
+btlogging.helpers module provides helper functions for the Bittensor logging system.
+"""
+
 import logging
+from typing import Generator
 
 
-def all_loggers():
-    """
-    Generator that yields all logger instances in the application.
+def all_loggers() -> Generator[logging.Logger, None, None]:
+    """Generator that yields all logger instances in the application.
+
+    Iterates through the logging root manager's logger dictionary and yields all active `Logger` instances. It skips
+    placeholders and other types that are not instances of `Logger`.
+
+    Yields:
+        logger (logging.Logger): An active logger instance.
     """
     for logger in logging.root.manager.loggerDict.values():
         if isinstance(logger, logging.PlaceHolder):
@@ -20,7 +47,16 @@ def all_loggers():
             pass
 
 
-def all_logger_names():
+def all_logger_names() -> Generator[str, None, None]:
+    """
+    Generate the names of all active loggers.
+
+    This function iterates through the logging root manager's logger dictionary and yields the names of all active
+    `Logger` instances. It skips placeholders and other types that are not instances of `Logger`.
+
+    Yields:
+        name (str): The name of an active logger.
+    """
     for name, logger in logging.root.manager.loggerDict.items():
         if isinstance(logger, logging.PlaceHolder):
             continue
@@ -36,7 +72,15 @@ def all_logger_names():
             pass
 
 
-def get_max_logger_name_length():
+def get_max_logger_name_length() -> int:
+    """
+    Calculate and return the length of the longest logger name.
+
+    This function iterates through all active logger names and determines the length of the longest name.
+
+    Returns:
+        max_length (int): The length of the longest logger name.
+    """
     max_length = 0
     for name in all_logger_names():
         if len(name) > max_length:
diff --git a/bittensor/btlogging/loggingmachine.py b/bittensor/btlogging/loggingmachine.py
index 69fdc9e69d..1c6aad3bb6 100644
--- a/bittensor/btlogging/loggingmachine.py
+++ b/bittensor/btlogging/loggingmachine.py
@@ -1,17 +1,39 @@
-import os
-import sys
-import copy
-import atexit
+# The MIT License (MIT)
+# Copyright © 2023 OpenTensor Foundation
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""
+Module provides a logging framework for Bittensor, managing both Bittensor-specific and third-party logging states.
+It leverages the StateMachine from the statemachine package to transition between different logging states such as
+Default, Debug, Trace, and Disabled.
+"""
+
 import argparse
-import multiprocessing as mp
+import atexit
+import copy
 import logging as stdlogging
+import multiprocessing as mp
+import os
+import sys
+from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
 from typing import NamedTuple
+
 from statemachine import StateMachine, State
-from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
 
 import bittensor.config
-from bittensor.btlogging.format import BtStreamFormatter, BtFileFormatter
-from bittensor.btlogging.helpers import all_loggers
 from bittensor.btlogging.defines import (
     TRACE_LOG_FORMAT,
     DATE_FORMAT,
@@ -20,9 +42,13 @@
     DEFAULT_MAX_ROTATING_LOG_FILE_SIZE,
     DEFAULT_LOG_BACKUP_COUNT,
 )
+from bittensor.btlogging.format import BtStreamFormatter, BtFileFormatter
+from bittensor.btlogging.helpers import all_loggers
 
 
 class LoggingConfig(NamedTuple):
+    """Named tuple to hold the logging configuration."""
+
     debug: bool
     trace: bool
     record_log: bool
@@ -30,9 +56,7 @@ class LoggingConfig(NamedTuple):
 
 
 class LoggingMachine(StateMachine):
-    """
-    Handles logger states for bittensor and 3rd party libraries
-    """
+    """Handles logger states for bittensor and 3rd party libraries."""
 
     Default = State(initial=True)
     Debug = State()
@@ -46,19 +70,19 @@ class LoggingMachine(StateMachine):
         | Default.to(Default)
     )
 
-    enable_trace = (
+    enable_trace: Trace = (
         Default.to(Trace) | Debug.to(Trace) | Disabled.to(Trace) | Trace.to(Trace)
     )
 
-    enable_debug = (
+    enable_debug: Debug = (
         Default.to(Debug) | Trace.to(Debug) | Disabled.to(Debug) | Debug.to(Debug)
     )
 
-    disable_trace = Trace.to(Default)
+    disable_trace: Default = Trace.to(Default)
 
-    disable_debug = Debug.to(Default)
+    disable_debug: Default = Debug.to(Default)
 
-    disable_logging = (
+    disable_logging: Disabled = (
         Trace.to(Disabled)
         | Debug.to(Disabled)
         | Default.to(Disabled)
@@ -69,7 +93,7 @@ def __init__(self, config: bittensor.config, name: str = BITTENSOR_LOGGER_NAME):
         # basics
         super(LoggingMachine, self).__init__()
         self._queue = mp.Queue(-1)
-        self._name = name
+        self._primary_loggers = {name}
         self._config = config
 
         # Formatters
@@ -89,7 +113,7 @@ def __init__(self, config: bittensor.config, name: str = BITTENSOR_LOGGER_NAME):
         self._listener = self._create_and_start_listener(self._handlers)
 
         # set up all the loggers
-        self._logger = self._initialize_bt_logger(name, config)
+        self._logger = self._initialize_bt_logger(name)
         self.disable_third_party_loggers()
 
     def _configure_handlers(self, config) -> list[stdlogging.Handler]:
@@ -113,14 +137,11 @@ def get_config(self):
         return self._config
 
     def set_config(self, config):
-        """
-        Set config after initialization, if desired.
-        """
+        """Set config after initialization, if desired."""
         self._config = config
         if config.logging_dir and config.record_log:
-            logfile = os.path.abspath(
-                os.path.join(config.logging_dir, DEFAULT_LOG_FILE_NAME)
-            )
+            expanded_dir = os.path.expanduser(config.logging_dir)
+            logfile = os.path.abspath(os.path.join(expanded_dir, DEFAULT_LOG_FILE_NAME))
             self._enable_file_logging(logfile)
         if config.trace:
             self.enable_trace()
@@ -131,8 +152,7 @@ def _create_and_start_listener(self, handlers):
         """
         A listener to receive and publish log records.
 
-        This listener receives records from a queue populated by the main bittensor
-        logger, as well as 3rd party loggers.
+        This listener receives records from a queue populated by the main bittensor logger, as well as 3rd party loggers
         """
 
         listener = QueueListener(self._queue, *handlers, respect_handler_level=True)
@@ -144,24 +164,30 @@ def get_queue(self):
         """
         Get the queue the QueueListener is publishing from.
 
-        To set up logging in a separate process, a QueueHandler must
-        be added to all the desired loggers.
+        To set up logging in a separate process, a QueueHandler must be added to all the desired loggers.
         """
         return self._queue
 
-    def _initialize_bt_logger(self, name, config):
+    def _initialize_bt_logger(self, name):
         """
         Initialize logging for bittensor.
 
-        Since the initial state is Default, logging level for the module logger
-        is INFO, and all third-party loggers are silenced. Subsequent state
-        transitions will handle all logger outputs.
+        Since the initial state is Default, logging level for the module logger is INFO, and all third-party loggers are
+        silenced. Subsequent state transitions will handle all logger outputs.
         """
         logger = stdlogging.getLogger(name)
         queue_handler = QueueHandler(self._queue)
         logger.addHandler(queue_handler)
         return logger
 
+    def _deinitialize_bt_logger(self, name):
+        """Find the logger by name and remove the queue handler associated with it."""
+        logger = stdlogging.getLogger(name)
+        for handler in list(logger.handlers):
+            if isinstance(handler, QueueHandler):
+                logger.removeHandler(handler)
+        return logger
+
     def _create_file_handler(self, logfile: str):
         file_handler = RotatingFileHandler(
             logfile,
@@ -172,18 +198,41 @@ def _create_file_handler(self, logfile: str):
         file_handler.setLevel(stdlogging.TRACE)
         return file_handler
 
+    def register_primary_logger(self, name: str):
+        """
+        Register a logger as primary logger
+
+        This adds a logger to the _primary_loggers set to ensure
+        it doesn't get disabled when disabling third-party loggers.
+        A queue handler is also associated with it.
+        """
+        self._primary_loggers.add(name)
+        self._initialize_bt_logger(name)
+
+    def deregister_primary_logger(self, name: str):
+        """
+        De-registers a primary logger
+
+        This function removes the logger from the _primary_loggers
+        set and deinitializes its queue handler
+        """
+        self._primary_loggers.remove(name)
+        self._deinitialize_bt_logger(name)
+
     def enable_third_party_loggers(self):
+        """Enables logging for third-party loggers by adding a queue handler to each."""
         for logger in all_loggers():
-            if logger.name == self._name:
+            if logger.name in self._primary_loggers:
                 continue
             queue_handler = QueueHandler(self._queue)
             logger.addHandler(queue_handler)
             logger.setLevel(self._logger.level)
 
     def disable_third_party_loggers(self):
+        """Disables logging for third-party loggers by removing all their handlers."""
         # remove all handlers
         for logger in all_loggers():
-            if logger.name == self._name:
+            if logger.name in self._primary_loggers:
                 continue
             for handler in logger.handlers:
                 logger.removeHandler(handler)
@@ -201,17 +250,20 @@ def _enable_file_logging(self, logfile: str):
 
     # state transitions
     def before_transition(self, event, state):
+        """Stops listener after transition."""
         self._listener.stop()
 
     def after_transition(self, event, state):
+        """Starts listener after transition."""
         self._listener.start()
 
     # Default Logging
     def before_enable_default(self):
+        """Logs status before enable Default."""
         self._logger.info(f"Enabling default logging.")
         self._logger.setLevel(stdlogging.INFO)
         for logger in all_loggers():
-            if logger.name == self._name:
+            if logger.name in self._primary_loggers:
                 continue
             logger.setLevel(stdlogging.CRITICAL)
 
@@ -220,94 +272,128 @@ def after_enable_default(self):
 
     # Trace
     def before_enable_trace(self):
+        """Logs status before enable Trace."""
         self._logger.info("Enabling trace.")
         self._stream_formatter.set_trace(True)
         for logger in all_loggers():
             logger.setLevel(stdlogging.TRACE)
 
     def after_enable_trace(self):
+        """Logs status after enable Trace."""
         self._logger.info("Trace enabled.")
 
     def before_disable_trace(self):
+        """Logs status before disable Trace."""
         self._logger.info(f"Disabling trace.")
         self._stream_formatter.set_trace(False)
         self.enable_default()
 
     def after_disable_trace(self):
+        """Logs status after disable Trace."""
         self._logger.info("Trace disabled.")
 
     # Debug
     def before_enable_debug(self):
+        """Logs status before enable Debug."""
         self._logger.info("Enabling debug.")
         self._stream_formatter.set_trace(True)
         for logger in all_loggers():
             logger.setLevel(stdlogging.DEBUG)
 
     def after_enable_debug(self):
+        """Logs status after enable Debug."""
         self._logger.info("Debug enabled.")
 
     def before_disable_debug(self):
+        """Logs status before disable Debug."""
         self._logger.info("Disabling debug.")
         self._stream_formatter.set_trace(False)
         self.enable_default()
 
     def after_disable_debug(self):
+        """Logs status after disable Debug."""
         self._logger.info("Debug disabled.")
 
     # Disable Logging
     def before_disable_logging(self):
+        """
+        Prepares the logging system for disabling.
+
+        This method performs the following actions:
+        1. Logs an informational message indicating that logging is being disabled.
+        2. Disables trace mode in the stream formatter.
+        3. Sets the logging level to CRITICAL for all loggers.
+
+        This ensures that only critical messages will be logged after this method is called.
+        """
         self._logger.info("Disabling logging.")
         self._stream_formatter.set_trace(False)
 
         for logger in all_loggers():
             logger.setLevel(stdlogging.CRITICAL)
 
-    # Required API
-    # support log commands for API backwards compatibility
+    # Required API support log commands for API backwards compatibility.
     @property
-    def __trace_on__(self):
+    def __trace_on__(self) -> bool:
+        """
+        Checks if the current state is in "Trace" mode.
+
+        Returns:
+            bool: True if the current state is "Trace", otherwise False.
+        """
         return self.current_state_value == "Trace"
 
-    def trace(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def trace(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps trace message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.trace(msg, *args, **kwargs)
 
-    def debug(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def debug(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps debug message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.debug(msg, *args, **kwargs)
 
-    def info(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def info(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps info message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.info(msg, *args, **kwargs)
 
-    def success(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def success(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps success message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.success(msg, *args, **kwargs)
 
-    def warning(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def warning(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps warning message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.warning(msg, *args, **kwargs)
 
-    def error(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def error(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps error message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.error(msg, *args, **kwargs)
 
-    def critical(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def critical(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps critical message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.critical(msg, *args, **kwargs)
 
-    def exception(self, msg="", prefix="", sufix="", *args, **kwargs):
-        msg = f"{prefix} - {msg} - {sufix}"
+    def exception(self, msg="", prefix="", suffix="", *args, **kwargs):
+        """Wraps exception message with prefix and suffix."""
+        msg = f"{prefix} - {msg} - {suffix}"
         self._logger.exception(msg, *args, **kwargs)
 
     def on(self):
+        """Enable default state."""
         self._logger.info("Logging enabled.")
         self.enable_default()
 
     def off(self):
+        """Disables all states."""
         self.disable_logging()
 
     def set_debug(self, on: bool = True):
+        """Sets Debug state."""
         if on and not self.current_state_value == "Debug":
             self.enable_debug()
         elif not on:
@@ -315,13 +401,15 @@ def set_debug(self, on: bool = True):
                 self.disable_debug()
 
     def set_trace(self, on: bool = True):
+        """Sets Trace state."""
         if on and not self.current_state_value == "Trace":
             self.enable_trace()
         elif not on:
             if self.current_state_value == "Trace":
                 self.disable_trace()
 
-    def get_level(self):
+    def get_level(self) -> int:
+        """Returns Logging level."""
         return self._logger.level
 
     def check_config(self, config: bittensor.config):
@@ -333,7 +421,7 @@ def help(self):
     @classmethod
     def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None):
         """Accept specific arguments fro parser"""
-        prefix_str = "" if prefix == None else prefix + "."
+        prefix_str = "" if prefix is None else prefix + "."
         try:
             default_logging_debug = os.getenv("BT_LOGGING_DEBUG") or False
             default_logging_trace = os.getenv("BT_LOGGING_TRACE") or False
@@ -370,11 +458,11 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None):
             pass
 
     @classmethod
-    def config(cls):
+    def config(cls) -> bittensor.config:
         """Get config from the argument parser.
 
         Return:
-            bittensor.config object
+            config (bittensor.config): config object
         """
         parser = argparse.ArgumentParser()
         cls.add_args(parser)
diff --git a/bittensor/chain_data.py b/bittensor/chain_data.py
index 05b36a8659..ea59a4d69d 100644
--- a/bittensor/chain_data.py
+++ b/bittensor/chain_data.py
@@ -14,9 +14,7 @@
 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
-import torch
 import bittensor
-
 import json
 from enum import Enum
 from dataclasses import dataclass, asdict
@@ -28,6 +26,7 @@
 
 from .utils import networking as net, U16_MAX, U16_NORMALIZED_FLOAT
 from .utils.balance import Balance
+from .utils.registration import torch, use_torch
 
 custom_rpc_type_registry = {
     "types": {
@@ -265,16 +264,43 @@ def from_neuron_info(cls, neuron_info: dict) -> "AxonInfo":
             coldkey=neuron_info["coldkey"],
         )
 
-    def to_parameter_dict(self) -> "torch.nn.ParameterDict":
-        r"""Returns a torch tensor of the subnet info."""
-        return torch.nn.ParameterDict(self.__dict__)
+    def _to_parameter_dict(
+        self, return_type: str
+    ) -> Union[dict[str, Union[int, str]], "torch.nn.ParameterDict"]:
+        if return_type == "torch":
+            return torch.nn.ParameterDict(self.__dict__)
+        else:
+            return self.__dict__
+
+    def to_parameter_dict(
+        self,
+    ) -> Union[dict[str, Union[int, str]], "torch.nn.ParameterDict"]:
+        """Returns a torch tensor or dict of the subnet info, depending on the USE_TORCH flag set"""
+        if use_torch():
+            return self._to_parameter_dict("torch")
+        else:
+            return self._to_parameter_dict("numpy")
+
+    @classmethod
+    def _from_parameter_dict(
+        cls,
+        parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"],
+        return_type: str,
+    ) -> "AxonInfo":
+        if return_type == "torch":
+            return cls(**dict(parameter_dict))
+        else:
+            return cls(**parameter_dict)
 
     @classmethod
     def from_parameter_dict(
-        cls, parameter_dict: "torch.nn.ParameterDict"
+        cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
     ) -> "AxonInfo":
-        r"""Returns an axon_info object from a torch parameter_dict."""
-        return cls(**dict(parameter_dict))
+        """Returns an axon_info object from a torch parameter_dict or a parameter dict."""
+        if use_torch():
+            return cls._from_parameter_dict(parameter_dict, "torch")
+        else:
+            return cls._from_parameter_dict(parameter_dict, "numpy")
 
 
 class ChainDataType(Enum):
@@ -697,7 +723,19 @@ def fix_decoded_values(cls, prometheus_info_decoded: Dict) -> "PrometheusInfo":
 @dataclass
 class DelegateInfo:
     r"""
-    Dataclass for delegate info.
+    Dataclass for delegate information.
+
+    Args:
+        hotkey_ss58 (str): Hotkey of the delegate for which the information is being fetched.
+        total_stake (int): Total stake of the delegate.
+        nominators (list[Tuple[str, int]]): List of nominators of the delegate and their stake.
+        take (float): Take of the delegate as a percentage.
+        owner_ss58 (str): Coldkey of the owner.
+        registrations (list[int]): List of subnets that the delegate is registered on.
+        validator_permits (list[int]): List of subnets that the delegate is allowed to validate on.
+        return_per_1000 (int): Return per 1000 TAO, for the delegate over a day.
+        total_daily_return (int): Total daily return of the delegate.
+
     """
 
     hotkey_ss58: str  # Hotkey of delegate
@@ -829,7 +867,7 @@ def list_of_tuple_from_vec_u8(
     ) -> Dict[str, List["StakeInfo"]]:
         r"""Returns a list of StakeInfo objects from a ``vec_u8``."""
         decoded: Optional[
-            List[Tuple(str, List[object])]
+            list[tuple[str, list[object]]]
         ] = from_scale_encoding_using_type_string(
             input=vec_u8, type_string="Vec<(AccountId, Vec<StakeInfo>)>"
         )
@@ -942,17 +980,42 @@ def fix_decoded_values(cls, decoded: Dict) -> "SubnetInfo":
             owner_ss58=ss58_encode(decoded["owner"], bittensor.__ss58_format__),
         )
 
-    def to_parameter_dict(self) -> "torch.nn.ParameterDict":
-        r"""Returns a torch tensor of the subnet info."""
-        return torch.nn.ParameterDict(self.__dict__)
+    def _to_parameter_dict(
+        self, return_type: str
+    ) -> Union[dict[str, Any], "torch.nn.ParameterDict"]:
+        if return_type == "torch":
+            return torch.nn.ParameterDict(self.__dict__)
+        else:
+            return self.__dict__
+
+    def to_parameter_dict(self) -> Union[dict[str, Any], "torch.nn.ParameterDict"]:
+        """Returns a torch tensor or dict of the subnet info."""
+        if use_torch():
+            return self._to_parameter_dict("torch")
+        else:
+            return self._to_parameter_dict("numpy")
 
     @classmethod
-    def from_parameter_dict(
+    def _from_parameter_dict_torch(
         cls, parameter_dict: "torch.nn.ParameterDict"
     ) -> "SubnetInfo":
-        r"""Returns a SubnetInfo object from a torch parameter_dict."""
+        """Returns a SubnetInfo object from a torch parameter_dict."""
         return cls(**dict(parameter_dict))
 
+    @classmethod
+    def _from_parameter_dict_numpy(cls, parameter_dict: dict[str, Any]) -> "SubnetInfo":
+        r"""Returns a SubnetInfo object from a parameter_dict."""
+        return cls(**parameter_dict)
+
+    @classmethod
+    def from_parameter_dict(
+        cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
+    ) -> "SubnetInfo":
+        if use_torch():
+            return cls._from_parameter_dict_torch(parameter_dict)
+        else:
+            return cls._from_parameter_dict_numpy(parameter_dict)
+
 
 @dataclass
 class SubnetHyperparameters:
@@ -1038,17 +1101,46 @@ def fix_decoded_values(cls, decoded: Dict) -> "SubnetHyperparameters":
             difficulty=decoded["difficulty"],
         )
 
-    def to_parameter_dict(self) -> "torch.nn.ParameterDict":
-        r"""Returns a torch tensor of the subnet hyperparameters."""
-        return torch.nn.ParameterDict(self.__dict__)
+    def _to_parameter_dict_torch(
+        self, return_type: str
+    ) -> Union[dict[str, Union[int, float, bool]], "torch.nn.ParameterDict"]:
+        if return_type == "torch":
+            return torch.nn.ParameterDict(self.__dict__)
+        else:
+            return self.__dict__
+
+    def to_parameter_dict(
+        self,
+    ) -> Union[dict[str, Union[int, float, bool]], "torch.nn.ParameterDict"]:
+        """Returns a torch tensor or dict of the subnet hyperparameters."""
+        if use_torch():
+            return self._to_parameter_dict_torch("torch")
+        else:
+            return self._to_parameter_dict_torch("numpy")
 
     @classmethod
-    def from_parameter_dict(
+    def _from_parameter_dict_torch(
         cls, parameter_dict: "torch.nn.ParameterDict"
-    ) -> "SubnetInfo":
-        r"""Returns a SubnetHyperparameters object from a torch parameter_dict."""
+    ) -> "SubnetHyperparameters":
+        """Returns a SubnetHyperparameters object from a torch parameter_dict."""
         return cls(**dict(parameter_dict))
 
+    @classmethod
+    def _from_parameter_dict_numpy(
+        cls, parameter_dict: dict[str, Any]
+    ) -> "SubnetHyperparameters":
+        """Returns a SubnetHyperparameters object from a parameter_dict."""
+        return cls(**parameter_dict)
+
+    @classmethod
+    def from_parameter_dict(
+        cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
+    ) -> "SubnetHyperparameters":
+        if use_torch():
+            return cls._from_parameter_dict_torch(parameter_dict)
+        else:
+            return cls._from_parameter_dict_numpy(parameter_dict)
+
 
 @dataclass
 class IPInfo:
@@ -1103,15 +1195,45 @@ def fix_decoded_values(cls, decoded: Dict) -> "IPInfo":
             protocol=decoded["ip_type_and_protocol"] & 0xF,
         )
 
-    def to_parameter_dict(self) -> "torch.nn.ParameterDict":
-        r"""Returns a torch tensor of the subnet info."""
-        return torch.nn.ParameterDict(self.__dict__)
+    def _to_parameter_dict(
+        self, return_type: str
+    ) -> Union[dict[str, Union[str, int]], "torch.nn.ParameterDict"]:
+        """Returns a torch tensor of the subnet info."""
+        if return_type == "torch":
+            return torch.nn.ParameterDict(self.__dict__)
+        else:
+            return self.__dict__
+
+    def to_parameter_dict(
+        self,
+    ) -> Union[dict[str, Union[str, int]], "torch.nn.ParameterDict"]:
+        """Returns a torch tensor or dict of the subnet IP info."""
+        if use_torch():
+            return self._to_parameter_dict("torch")
+        else:
+            return self._to_parameter_dict("numpy")
 
     @classmethod
-    def from_parameter_dict(cls, parameter_dict: "torch.nn.ParameterDict") -> "IPInfo":
-        r"""Returns a IPInfo object from a torch parameter_dict."""
+    def _from_parameter_dict_torch(
+        cls, parameter_dict: "torch.nn.ParameterDict"
+    ) -> "IPInfo":
+        """Returns a IPInfo object from a torch parameter_dict."""
         return cls(**dict(parameter_dict))
 
+    @classmethod
+    def _from_parameter_dict_numpy(cls, parameter_dict: dict[str, Any]) -> "IPInfo":
+        """Returns a IPInfo object from a parameter_dict."""
+        return cls(**parameter_dict)
+
+    @classmethod
+    def from_parameter_dict(
+        cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
+    ) -> "IPInfo":
+        if use_torch():
+            return cls._from_parameter_dict_torch(parameter_dict)
+        else:
+            return cls._from_parameter_dict_numpy(parameter_dict)
+
 
 # Senate / Proposal data
 
diff --git a/bittensor/cli.py b/bittensor/cli.py
index 0f708aca79..18c62480e3 100644
--- a/bittensor/cli.py
+++ b/bittensor/cli.py
@@ -248,8 +248,8 @@ def __init__(
         # If no_version_checking is not set or set as False in the config, version checking is done.
         if not self.config.get("no_version_checking", d=True):
             try:
-                bittensor.utils.version_checking()
-            except:
+                bittensor.utils.check_version()
+            except bittensor.utils.VersionCheckError:
                 # If version checking fails, inform user with an exception.
                 raise RuntimeError(
                     "To avoid internet-based version checking, pass --no_version_checking while running the CLI."
@@ -331,7 +331,7 @@ def check_config(config: "bittensor.config"):
             command_data = COMMANDS[command]
 
             if isinstance(command_data, dict):
-                if config["subcommand"] != None:
+                if config["subcommand"] is not None:
                     command_data["commands"][config["subcommand"]].check_config(config)
                 else:
                     console.print(
diff --git a/bittensor/commands/delegates.py b/bittensor/commands/delegates.py
index 5d7ff4fcd8..3cad50ecd8 100644
--- a/bittensor/commands/delegates.py
+++ b/bittensor/commands/delegates.py
@@ -15,23 +15,21 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-import sys
-import os
 import argparse
-import bittensor
-from typing import List, Optional
-from rich.table import Table
-from rich.prompt import Confirm, FloatPrompt, Prompt
+import os
+import sys
+from typing import List, Dict, Optional
+
 from rich.console import Text
-from tqdm import tqdm
+from rich.prompt import Prompt, FloatPrompt, Confirm
+from rich.table import Table
 from substrateinterface.exceptions import SubstrateRequestException
-from .utils import get_delegates_details, DelegatesDetails
-from .identity import SetIdentityCommand
-from . import defaults
+from tqdm import tqdm
 
-import os
 import bittensor
-from typing import List, Dict, Optional
+from . import defaults
+from .identity import SetIdentityCommand
+from .utils import get_delegates_details, DelegatesDetails
 
 
 def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]:
@@ -59,14 +57,15 @@ def show_delegates(
     The table is sorted by total stake in descending order and provides
     a snapshot of delegate performance and status, helping users make informed decisions for staking or nominating.
 
-    This is a helper function that is called by the :func:`list_delegates` and :func:`my_delegates`, and is not intended to be used directly in user code unless specifically required.
+    This is a helper function that is called by the :func:`list_delegates` and :func:`my_delegates`, and is not intended
+    to be used directly in user code unless specifically required.
 
     Args:
-        - delegates (List[bittensor.DelegateInfo]): A list of delegate information objects to be displayed.
-        - prev_delegates (Optional[List[bittensor.DelegateInfo]]): A list of delegate information objects from a previous state, used to calculate changes in stake. Defaults to ``None``.
-        - width (Optional[int]): The width of the console output table. Defaults to ``None``, which will make the table expand to the maximum width of the console.
+        delegates (List[bittensor.DelegateInfo]): A list of delegate information objects to be displayed.
+        prev_delegates (Optional[List[bittensor.DelegateInfo]]): A list of delegate information objects from a previous state, used to calculate changes in stake. Defaults to ``None``.
+        width (Optional[int]): The width of the console output table. Defaults to ``None``, which will make the table expand to the maximum width of the console.
 
-    The output table includes the following columns:
+    The output table contains the following columns:
 
     - INDEX: The numerical index of the delegate.
     - DELEGATE: The name of the delegate.
@@ -75,15 +74,14 @@ def show_delegates(
     - DELEGATE STAKE(τ): The stake that is directly delegated to the delegate.
     - TOTAL STAKE(τ): The total stake held by the delegate, including nominators' stake.
     - CHANGE/(4h): The percentage change in the delegate's stake over the past 4 hours.
-    - SUBNETS: A list of subnets the delegate is registered with.
     - VPERMIT: Validator permits held by the delegate for the subnets.
+    - TAKE: The percentage of the delegate's earnings taken by the network.
     - NOMINATOR/(24h)/kτ: The earnings per 1000 τ staked by nominators in the last 24 hours.
     - DELEGATE/(24h): The earnings of the delegate in the last 24 hours.
     - Desc: A brief description provided by the delegate.
 
     Usage:
-        This function is typically used within the Bittensor CLI to show current delegate
-        options to users who are considering where to stake their tokens.
+        This function is typically used within the Bittensor CLI to show current delegate options to users who are considering where to stake their tokens.
 
     Example usage::
 
@@ -91,7 +89,8 @@ def show_delegates(
 
     Note:
         This function is primarily for display purposes within a command-line interface and does
-        not return any values. It relies on the `rich <https://github.com/Textualize/rich>`_ Python library to render the table in the
+        not return any values. It relies on the `rich <https://github.com/Textualize/rich>`_ Python library to render
+        the table in the
         console.
     """
 
@@ -195,17 +194,29 @@ def show_delegates(
             rate_change_in_stake_str = "[grey0]NA[/grey0]"
 
         table.add_row(
+            # INDEX
             str(i),
+            # DELEGATE
             Text(delegate_name, style=f"link {delegate_url}"),
+            # SS58
             f"{delegate.hotkey_ss58:8.8}...",
+            # NOMINATORS
             str(len([nom for nom in delegate.nominators if nom[1].rao > 0])),
+            # DELEGATE STAKE
             f"{owner_stake!s:13.13}",
+            # TOTAL STAKE
             f"{delegate.total_stake!s:13.13}",
+            # CHANGE/(4h)
             rate_change_in_stake_str,
+            # VPERMIT
             str(delegate.registrations),
+            # TAKE
             f"{delegate.take * 100:.1f}%",
-            f"{bittensor.Balance.from_tao( delegate.total_daily_return.tao * (1000/ ( 0.001 + delegate.total_stake.tao ) ))!s:6.6}",
-            f"{bittensor.Balance.from_tao( delegate.total_daily_return.tao * (0.18) ) !s:6.6}",
+            # NOMINATOR/(24h)/k
+            f"{bittensor.Balance.from_tao( delegate.total_daily_return.tao * (1000/ (0.001 + delegate.total_stake.tao)))!s:6.6}",
+            # DELEGATE/(24h)
+            f"{bittensor.Balance.from_tao(delegate.total_daily_return.tao * 0.18) !s:6.6}",
+            # Desc
             str(delegate_description),
             end_section=True,
         )
@@ -224,10 +235,13 @@ class DelegateStakeCommand:
         - ``amount``: The amount of Tao to stake.
         - ``all``: If specified, the command stakes all available Tao.
 
-    The command interacts with the user to determine the delegate and the amount of Tao to be staked. If the ``--all`` flag is used, it delegates the entire available balance.
+    The command interacts with the user to determine the delegate and the amount of Tao to be staked. If the ``--all``
+    flag is used, it delegates the entire available balance.
 
     Usage:
-        The user must specify the delegate's SS58 address and the amount of Tao to stake. The function sends a transaction to the subtensor network to delegate the specified amount to the chosen delegate. These values are prompted if not provided.
+        The user must specify the delegate's SS58 address and the amount of Tao to stake. The function sends a
+        transaction to the subtensor network to delegate the specified amount to the chosen delegate. These values are
+        prompted if not provided.
 
     Example usage::
 
@@ -235,7 +249,9 @@ class DelegateStakeCommand:
         btcli delegate --delegate_ss58key <SS58_ADDRESS> --all
 
     Note:
-        This command modifies the blockchain state and may incur transaction fees. It requires user confirmation and interaction, and is designed to be used within the Bittensor CLI environment. The user should ensure the delegate's address and the amount to be staked are correct before executing the command.
+        This command modifies the blockchain state and may incur transaction fees. It requires user confirmation and
+        interaction, and is designed to be used within the Bittensor CLI environment. The user should ensure the
+        delegate's address and the amount to be staked are correct before executing the command.
     """
 
     @staticmethod
@@ -343,7 +359,8 @@ def check_config(config: "bittensor.config"):
 
 class DelegateUnstakeCommand:
     """
-    Executes the ``undelegate`` command, allowing users to withdraw their staked Tao from a delegate on the Bittensor network.
+    Executes the ``undelegate`` command, allowing users to withdraw their staked Tao from a delegate on the Bittensor
+    network.
 
     This process is known as "undelegating" and it reverses the delegation process, freeing up the staked tokens.
 
@@ -353,10 +370,13 @@ class DelegateUnstakeCommand:
         - ``amount``: The amount of Tao to undelegate.
         - ``all``: If specified, the command undelegates all staked Tao from the delegate.
 
-    The command prompts the user for the amount of Tao to undelegate and the ``SS58`` address of the delegate from which to undelegate. If the ``--all`` flag is used, it will attempt to undelegate the entire staked amount from the specified delegate.
+    The command prompts the user for the amount of Tao to undelegate and the ``SS58`` address of the delegate from which
+    to undelegate. If the ``--all`` flag is used, it will attempt to undelegate the entire staked amount from the
+    specified delegate.
 
     Usage:
-        The user must provide the delegate's SS58 address and the amount of Tao to undelegate. The function will then send a transaction to the Bittensor network to process the undelegation.
+        The user must provide the delegate's SS58 address and the amount of Tao to undelegate. The function will then
+        send a transaction to the Bittensor network to process the undelegation.
 
     Example usage::
 
@@ -364,7 +384,9 @@ class DelegateUnstakeCommand:
         btcli undelegate --delegate_ss58key <SS58_ADDRESS> --all
 
     Note:
-        This command can result in a change to the blockchain state and may incur transaction fees. It is interactive and requires confirmation from the user before proceeding. It should be used with care as undelegating can affect the delegate's total stake and
+        This command can result in a change to the blockchain state and may incur transaction fees. It is interactive
+        and requires confirmation from the user before proceeding. It should be used with care as undelegating can
+        affect the delegate's total stake and
         potentially the user's staking rewards.
     """
 
@@ -382,9 +404,9 @@ def run(cli: "bittensor.cli"):
                 subtensor.close()
                 bittensor.logging.debug("closing subtensor connection")
 
-    def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
+    def _run(self: "bittensor.cli", subtensor: "bittensor.subtensor"):
         """Undelegates stake from a chain delegate."""
-        config = cli.config.copy()
+        config = self.config.copy()
         wallet = bittensor.wallet(config=config)
         subtensor.undelegate(
             wallet=wallet,
@@ -480,7 +502,7 @@ class ListDelegatesCommand:
     """
     Displays a formatted table of Bittensor network delegates, providing a comprehensive overview of delegate statistics and information.
 
-    This table helps users make informed decisions on which delegates to allocate their Tao stake.
+    This table helps users make informed decisions on which delegates to allocate their TAO stake.
 
     Optional Arguments:
         - ``wallet.name``: The name of the wallet to use for the command.
@@ -501,7 +523,9 @@ class ListDelegatesCommand:
     - DELEGATE/(24h): The total earnings of the delegate in the last 24 hours.
     - DESCRIPTION: A brief description of the delegate's purpose and operations.
 
-    Sorting is done based on the ``TOTAL STAKE`` column in descending order. Changes in stake are highlighted: increases in green and decreases in red. Entries with no previous data are marked with ``NA``. Each delegate's name is a hyperlink to their respective URL, if available.
+    Sorting is done based on the ``TOTAL STAKE`` column in descending order. Changes in stake are highlighted:
+    increases in green and decreases in red. Entries with no previous data are marked with ``NA``. Each delegate's name
+    is a hyperlink to their respective URL, if available.
 
     Example usage::
 
@@ -510,7 +534,8 @@ class ListDelegatesCommand:
         btcli root list_delegates --subtensor.network finney # can also be `test` or `local`
 
     Note:
-        This function is part of the Bittensor CLI tools and is intended for use within a console application. It prints directly to the console and does not return any value.
+        This function is part of the Bittensor CLI tools and is intended for use within a console application. It prints
+        directly to the console and does not return any value.
     """
 
     @staticmethod
@@ -519,6 +544,10 @@ def run(cli: "bittensor.cli"):
         List all delegates on the network.
         """
         try:
+            cli.config.subtensor.network = "archive"
+            cli.config.subtensor.chain_endpoint = (
+                "wss://archive.chain.opentensor.ai:443"
+            )
             subtensor: "bittensor.subtensor" = bittensor.subtensor(
                 config=cli.config, log_verbose=False
             )
@@ -533,10 +562,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         r"""
         List all delegates on the network.
         """
-        cli.config.subtensor.network = "archive"
-        cli.config.subtensor.chain_endpoint = "wss://archive.chain.opentensor.ai:443"
         with bittensor.__console__.status(":satellite: Loading delegates..."):
-            delegates: bittensor.DelegateInfo = subtensor.get_delegates()
+            delegates: list[bittensor.DelegateInfo] = subtensor.get_delegates()
+
             try:
                 prev_delegates = subtensor.get_delegates(max(0, subtensor.block - 1200))
             except SubstrateRequestException:
@@ -569,7 +597,8 @@ class NominateCommand:
     """
     Executes the ``nominate`` command, which facilitates a wallet to become a delegate on the Bittensor network.
 
-    This command handles the nomination process, including wallet unlocking and verification of the hotkey's current delegate status.
+    This command handles the nomination process, including wallet unlocking and verification of the hotkey's current
+    delegate status.
 
     The command performs several checks:
 
@@ -583,7 +612,8 @@ class NominateCommand:
         - ``wallet.hotkey``: The name of the hotkey to use for the command.
 
     Usage:
-        To run the command, the user must have a configured wallet with both hotkey and coldkey. If the wallet is not already nominated, this command will initiate the process.
+        To run the command, the user must have a configured wallet with both hotkey and coldkey. If the wallet is not
+        already nominated, this command will initiate the process.
 
     Example usage::
 
@@ -591,7 +621,9 @@ class NominateCommand:
         btcli root nominate --wallet.name my_wallet --wallet.hotkey my_hotkey
 
     Note:
-        This function is intended to be used as a CLI command. It prints the outcome directly to the console and does not return any value. It should not be called programmatically in user code due to its interactive nature and side effects on the network state.
+        This function is intended to be used as a CLI command. It prints the outcome directly to the console and does
+        not return any value. It should not be called programmatically in user code due to its interactive nature and
+        side effects on the network state.
     """
 
     @staticmethod
@@ -683,7 +715,8 @@ def check_config(config: "bittensor.config"):
 
 class MyDelegatesCommand:
     """
-    Executes the ``my_delegates`` command within the Bittensor CLI, which retrieves and displays a table of delegated stakes from a user's wallet(s) to various delegates on the Bittensor network.
+    Executes the ``my_delegates`` command within the Bittensor CLI, which retrieves and displays a table of delegated
+    stakes from a user's wallet(s) to various delegates on the Bittensor network.
 
     The command provides detailed insights into the user's
     staking activities and the performance of their chosen delegates.
@@ -710,7 +743,9 @@ class MyDelegatesCommand:
     The command also sums and prints the total amount of Tao delegated across all wallets.
 
     Usage:
-        The command can be run as part of the Bittensor CLI suite of tools and requires no parameters if a single wallet is used. If multiple wallets are present, the ``--all`` flag can be specified to aggregate information across all wallets.
+        The command can be run as part of the Bittensor CLI suite of tools and requires no parameters if a single wallet
+        is used. If multiple wallets are present, the ``--all`` flag can be specified to aggregate information across
+        all wallets.
 
     Example usage::
 
@@ -740,7 +775,7 @@ def run(cli: "bittensor.cli"):
     def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         """Delegates stake to a chain delegate."""
         config = cli.config.copy()
-        if config.get("all", d=None) == True:
+        if config.get("all", d=None):
             wallets = _get_coldkey_wallets_for_path(config.wallet.path)
         else:
             wallets = [bittensor.wallet(config=config)]
@@ -895,17 +930,23 @@ def check_config(config: "bittensor.config"):
 class SetTakeCommand:
     """
     Executes the ``set_take`` command, which sets the delegate take.
+
     The command performs several checks:
+
         1. Hotkey is already a delegate
         2. New take value is within 0-18% range
+
     Optional Arguments:
         - ``take``: The new take value
         - ``wallet.name``: The name of the wallet to use for the command.
         - ``wallet.hotkey``: The name of the hotkey to use for the command.
+
     Usage:
         To run the command, the user must have a configured wallet with both hotkey and coldkey. Also, the hotkey should already be a delegate.
+
     Example usage::
         btcli root set_take --wallet.name my_wallet --wallet.hotkey my_hotkey
+
     Note:
         This function can be used to update the takes individually for every subnet
     """
@@ -950,9 +991,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
             new_take = float(new_take_str)
 
         if new_take > 0.18:
-            bittensor.__console__.print(
-                "ERROR: Take value should be in the range of 0 to 18%"
-            )
+            bittensor.__console__.print("ERROR: Take value should not exceed 18%")
             return
 
         result: bool = subtensor.set_take(
diff --git a/bittensor/commands/inspect.py b/bittensor/commands/inspect.py
index 3858810312..76b015b774 100644
--- a/bittensor/commands/inspect.py
+++ b/bittensor/commands/inspect.py
@@ -182,7 +182,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         )
         for wallet in tqdm(wallets):
             delegates: List[
-                Tuple(bittensor.DelegateInfo, bittensor.Balance)
+                Tuple[bittensor.DelegateInfo, bittensor.Balance]
             ] = subtensor.get_delegated(coldkey_ss58=wallet.coldkeypub.ss58_address)
             if not wallet.coldkeypub_file.exists_on_device():
                 continue
diff --git a/bittensor/commands/overview.py b/bittensor/commands/overview.py
index f60ed4d226..477ad9f01a 100644
--- a/bittensor/commands/overview.py
+++ b/bittensor/commands/overview.py
@@ -90,15 +90,12 @@ def run(cli: "bittensor.cli"):
                 subtensor.close()
                 bittensor.logging.debug("closing subtensor connection")
 
-    def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
-        r"""Prints an overview for the wallet's colkey."""
-        console = bittensor.__console__
-        wallet = bittensor.wallet(config=cli.config)
-
-        all_hotkeys = []
-        total_balance = bittensor.Balance(0)
-
-        # We are printing for every coldkey.
+    @staticmethod
+    def _get_total_balance(
+        total_balance: "bittensor.Balance",
+        subtensor: "bittensor.subtensor",
+        cli: "bittensor.cli",
+    ) -> Tuple[List["bittensor.wallet"], "bittensor.Balance"]:
         if cli.config.get("all", d=None):
             cold_wallets = get_coldkey_wallets_for_path(cli.config.wallet.path)
             for cold_wallet in tqdm(cold_wallets, desc="Pulling balances"):
@@ -122,26 +119,82 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
                 )
             if not coldkey_wallet.coldkeypub_file.exists_on_device():
                 console.print("[bold red]No wallets found.")
-                return
+                return [], None
             all_hotkeys = get_hotkey_wallets_for_wallet(coldkey_wallet)
 
-        # We are printing for a select number of hotkeys from all_hotkeys.
+        return all_hotkeys, total_balance
 
-        if cli.config.get("hotkeys", []):
-            if not cli.config.get("all_hotkeys", False):
-                # We are only showing hotkeys that are specified.
-                all_hotkeys = [
-                    hotkey
-                    for hotkey in all_hotkeys
-                    if hotkey.hotkey_str in cli.config.hotkeys
-                ]
+    @staticmethod
+    def _get_hotkeys(
+        cli: "bittensor.cli", all_hotkeys: List["bittensor.wallet"]
+    ) -> List["bittensor.wallet"]:
+        if not cli.config.get("all_hotkeys", False):
+            # We are only showing hotkeys that are specified.
+            all_hotkeys = [
+                hotkey
+                for hotkey in all_hotkeys
+                if hotkey.hotkey_str in cli.config.hotkeys
+            ]
+        else:
+            # We are excluding the specified hotkeys from all_hotkeys.
+            all_hotkeys = [
+                hotkey
+                for hotkey in all_hotkeys
+                if hotkey.hotkey_str not in cli.config.hotkeys
+            ]
+        return all_hotkeys
+
+    @staticmethod
+    def _get_key_address(all_hotkeys: List["bittensor.wallet"]):
+        hotkey_coldkey_to_hotkey_wallet = {}
+        for hotkey_wallet in all_hotkeys:
+            if hotkey_wallet.hotkey.ss58_address not in hotkey_coldkey_to_hotkey_wallet:
+                hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address] = {}
+
+            hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address][
+                hotkey_wallet.coldkeypub.ss58_address
+            ] = hotkey_wallet
+
+        all_hotkey_addresses = list(hotkey_coldkey_to_hotkey_wallet.keys())
+
+        return all_hotkey_addresses, hotkey_coldkey_to_hotkey_wallet
+
+    @staticmethod
+    def _process_neuron_results(
+        results: List[Tuple[int, List["bittensor.NeuronInfoLite"], Optional[str]]],
+        neurons: Dict[str, List["bittensor.NeuronInfoLite"]],
+        netuids: List[int],
+    ) -> Dict[str, List["bittensor.NeuronInfoLite"]]:
+        for result in results:
+            netuid, neurons_result, err_msg = result
+            if err_msg is not None:
+                console.print(f"netuid '{netuid}': {err_msg}")
+
+            if len(neurons_result) == 0:
+                # Remove netuid from overview if no neurons are found.
+                netuids.remove(netuid)
+                del neurons[str(netuid)]
             else:
-                # We are excluding the specified hotkeys from all_hotkeys.
-                all_hotkeys = [
-                    hotkey
-                    for hotkey in all_hotkeys
-                    if hotkey.hotkey_str not in cli.config.hotkeys
-                ]
+                # Add neurons to overview.
+                neurons[str(netuid)] = neurons_result
+        return neurons
+
+    def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
+        r"""Prints an overview for the wallet's colkey."""
+        console = bittensor.__console__
+        wallet = bittensor.wallet(config=cli.config)
+
+        all_hotkeys = []
+        total_balance = bittensor.Balance(0)
+
+        # We are printing for every coldkey.
+        all_hotkeys, total_balance = OverviewCommand._get_total_balance(
+            total_balance, subtensor, cli
+        )
+
+        # We are printing for a select number of hotkeys from all_hotkeys.
+        if cli.config.get("hotkeys"):
+            all_hotkeys = OverviewCommand._get_hotkeys(cli, all_hotkeys)
 
         # Check we have keys to display.
         if len(all_hotkeys) == 0:
@@ -161,21 +214,16 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         for netuid in netuids:
             neurons[str(netuid)] = []
 
-        all_wallet_names = set([wallet.name for wallet in all_hotkeys])
+        all_wallet_names = {wallet.name for wallet in all_hotkeys}
         all_coldkey_wallets = [
             bittensor.wallet(name=wallet_name) for wallet_name in all_wallet_names
         ]
 
-        hotkey_coldkey_to_hotkey_wallet = {}
-        for hotkey_wallet in all_hotkeys:
-            if hotkey_wallet.hotkey.ss58_address not in hotkey_coldkey_to_hotkey_wallet:
-                hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address] = {}
-
-            hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address][
-                hotkey_wallet.coldkeypub.ss58_address
-            ] = hotkey_wallet
+        (
+            all_hotkey_addresses,
+            hotkey_coldkey_to_hotkey_wallet,
+        ) = OverviewCommand._get_key_address(all_hotkeys)
 
-        all_hotkey_addresses = list(hotkey_coldkey_to_hotkey_wallet.keys())
         with console.status(
             ":satellite: Syncing with chain: [white]{}[/white] ...".format(
                 cli.config.subtensor.get(
@@ -198,18 +246,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
                 )
                 executor.shutdown(wait=True)  # wait for all complete
 
-                for result in results:
-                    netuid, neurons_result, err_msg = result
-                    if err_msg is not None:
-                        console.print(f"netuid '{netuid}': {err_msg}")
-
-                    if len(neurons_result) == 0:
-                        # Remove netuid from overview if no neurons are found.
-                        netuids.remove(netuid)
-                        del neurons[str(netuid)]
-                    else:
-                        # Add neurons to overview.
-                        neurons[str(netuid)] = neurons_result
+                neurons = OverviewCommand._process_neuron_results(
+                    results, neurons, netuids
+                )
 
             total_coldkey_stake_from_metagraph = defaultdict(
                 lambda: bittensor.Balance(0.0)
@@ -249,7 +288,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
                     )
                 )
 
-            if len(coldkeys_to_check) > 0:
+            if coldkeys_to_check:
                 # We have some stake that is not with a registered hotkey.
                 if "-1" not in neurons:
                     neurons["-1"] = []
@@ -294,7 +333,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
                     wallet_.hotkey_ss58 = hotkey_addr
                     wallet.hotkey_str = hotkey_addr[:5]  # Max length of 5 characters
                     # Indicates a hotkey not on local machine but exists in stake_info obj on-chain
-                    if hotkey_coldkey_to_hotkey_wallet.get(hotkey_addr) == None:
+                    if hotkey_coldkey_to_hotkey_wallet.get(hotkey_addr) is None:
                         hotkey_coldkey_to_hotkey_wallet[hotkey_addr] = {}
                     hotkey_coldkey_to_hotkey_wallet[hotkey_addr][
                         coldkey_wallet.coldkeypub.ss58_address
diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py
index 72fdb6853a..8b21a33304 100644
--- a/bittensor/commands/register.py
+++ b/bittensor/commands/register.py
@@ -216,7 +216,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
             )
             sys.exit(1)
 
-        subtensor.register(
+        registered = subtensor.register(
             wallet=wallet,
             netuid=cli.config.netuid,
             prompt=not cli.config.no_prompt,
@@ -234,6 +234,8 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
                 "verbose", defaults.pow_register.verbose
             ),
         )
+        if not registered:
+            sys.exit(1)
 
     @staticmethod
     def add_args(parser: argparse.ArgumentParser):
@@ -408,7 +410,7 @@ def run(cli: "bittensor.cli"):
     def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         r"""Register neuron."""
         wallet = bittensor.wallet(config=cli.config)
-        subtensor.run_faucet(
+        success = subtensor.run_faucet(
             wallet=wallet,
             prompt=not cli.config.no_prompt,
             tpb=cli.config.pow_register.cuda.get("tpb", None),
@@ -425,6 +427,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
                 "verbose", defaults.pow_register.verbose
             ),
         )
+        if not success:
+            bittensor.logging.error("Faucet run failed.")
+            sys.exit(1)
 
     @staticmethod
     def add_args(parser: argparse.ArgumentParser):
diff --git a/bittensor/commands/root.py b/bittensor/commands/root.py
index 7cc8cbce70..a3658d03ea 100644
--- a/bittensor/commands/root.py
+++ b/bittensor/commands/root.py
@@ -16,7 +16,6 @@
 # DEALINGS IN THE SOFTWARE.
 
 import re
-import torch
 import typing
 import argparse
 import numpy as np
@@ -283,7 +282,6 @@ def run(cli: "bittensor.cli"):
     def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         r"""Set weights for root network."""
         wallet = bittensor.wallet(config=cli.config)
-        subnets: List[bittensor.SubnetInfo] = subtensor.get_all_subnets_info()
 
         root = subtensor.metagraph(0, lite=False)
         try:
@@ -301,7 +299,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
             f"Boosting weight for netuid {cli.config.netuid} from {prev_weight} -> {new_weight}"
         )
         my_weights[cli.config.netuid] = new_weight
-        all_netuids = torch.tensor(list(range(len(my_weights))))
+        all_netuids = np.arange(len(my_weights))
 
         bittensor.__console__.print("Setting root weights...")
         subtensor.root_set_weights(
@@ -401,7 +399,6 @@ def run(cli: "bittensor.cli"):
     @staticmethod
     def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         wallet = bittensor.wallet(config=cli.config)
-        subnets: List[bittensor.SubnetInfo] = subtensor.get_all_subnets_info()
 
         bittensor.__console__.print(
             "Slashing weight for subnet: {} by amount: {}".format(
@@ -419,7 +416,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
         my_weights = root.weights[my_uid]
         my_weights[cli.config.netuid] -= cli.config.amount
         my_weights[my_weights < 0] = 0  # Ensure weights don't go negative
-        all_netuids = torch.tensor(list(range(len(my_weights))))
+        all_netuids = np.arange(len(my_weights))
 
         subtensor.root_set_weights(
             wallet=wallet,
@@ -520,13 +517,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
             cli.config.weights = Prompt.ask(f"Enter weights (e.g. {example})")
 
         # Parse from string
-        netuids = torch.tensor(
-            list(map(int, re.split(r"[ ,]+", cli.config.netuids))), dtype=torch.long
-        )
-        weights = torch.tensor(
-            list(map(float, re.split(r"[ ,]+", cli.config.weights))),
-            dtype=torch.float32,
-        )
+        matched_netuids = list(map(int, re.split(r"[ ,]+", cli.config.netuids)))
+        netuids = np.array(matched_netuids, dtype=np.int64)
+
+        matched_weights = [
+            float(weight) for weight in re.split(r"[ ,]+", cli.config.weights)
+        ]
+        weights = np.array(matched_weights, dtype=np.float32)
 
         # Run the set weights operation.
         subtensor.root_set_weights(
diff --git a/bittensor/commands/utils.py b/bittensor/commands/utils.py
index bcf9826626..4ea8fa3dd1 100644
--- a/bittensor/commands/utils.py
+++ b/bittensor/commands/utils.py
@@ -17,11 +17,11 @@
 
 import sys
 import os
-import torch
 import bittensor
+import requests
+from bittensor.utils.registration import torch
 from typing import List, Dict, Any, Optional
 from rich.prompt import Confirm, PromptBase
-import requests
 from dataclasses import dataclass
 from . import defaults
 
@@ -78,9 +78,9 @@ def check_netuid_set(
 
 def check_for_cuda_reg_config(config: "bittensor.config") -> None:
     """Checks, when CUDA is available, if the user would like to register with their CUDA device."""
-    if torch.cuda.is_available():
+    if torch and torch.cuda.is_available():
         if not config.no_prompt:
-            if config.pow_register.cuda.get("use_cuda") == None:  # flag not set
+            if config.pow_register.cuda.get("use_cuda") is None:  # flag not set
                 # Ask about cuda registration only if a CUDA device is available.
                 cuda = Confirm.ask("Detected CUDA device, use CUDA for registration?\n")
                 config.pow_register.cuda.use_cuda = cuda
diff --git a/bittensor/dendrite.py b/bittensor/dendrite.py
index 2b7afee5ef..22e4ad93f4 100644
--- a/bittensor/dendrite.py
+++ b/bittensor/dendrite.py
@@ -22,15 +22,15 @@
 import asyncio
 import uuid
 import time
-import torch
 import aiohttp
 import bittensor
-from typing import Union, Optional, List, Union, AsyncGenerator, Any
+from typing import Optional, List, Union, AsyncGenerator, Any
+from bittensor.utils.registration import torch, use_torch
 
 
-class dendrite(torch.nn.Module):
+class DendriteMixin:
     """
-    The Dendrite class, inheriting from PyTorch's Module class, represents the abstracted implementation of a network client module.
+    The Dendrite class represents the abstracted implementation of a network client module.
 
     In the brain analogy, dendrites receive signals
     from other neurons (in this case, network servers or axons), and the Dendrite class here is designed
@@ -105,7 +105,7 @@ def __init__(
                 The user's wallet or keypair used for signing messages. Defaults to ``None``, in which case a new :func:`bittensor.wallet().hotkey` is generated and used.
         """
         # Initialize the parent class
-        super(dendrite, self).__init__()
+        super(DendriteMixin, self).__init__()
 
         # Unique identifier for the instance
         self.uuid = str(uuid.uuid1())
@@ -444,7 +444,7 @@ async def single_axon_response(
                     # If in streaming mode, return the async_generator
                     return self.call_stream(
                         target_axon=target_axon,
-                        synapse=synapse.copy(),  # type: ignore
+                        synapse=synapse.model_copy(),  # type: ignore
                         timeout=timeout,
                         deserialize=deserialize,
                     )
@@ -452,7 +452,7 @@ async def single_axon_response(
                     # If not in streaming mode, simply call the axon and get the response.
                     return await self.call(
                         target_axon=target_axon,
-                        synapse=synapse.copy(),  # type: ignore
+                        synapse=synapse.model_copy(),  # type: ignore
                         timeout=timeout,
                         deserialize=deserialize,
                     )
@@ -519,7 +519,7 @@ async def call(
             async with (await self.session).post(
                 url,
                 headers=synapse.to_headers(),
-                json=synapse.dict(),
+                json=synapse.model_dump(),
                 timeout=timeout,
             ) as response:
                 # Extract the JSON response from the server
@@ -601,7 +601,7 @@ async def call_stream(
             async with (await self.session).post(
                 url,
                 headers=synapse.to_headers(),
-                json=synapse.dict(),
+                json=synapse.model_dump(),
                 timeout=timeout,
             ) as response:
                 # Use synapse subclass' process_streaming_response method to yield the response chunks
@@ -698,9 +698,9 @@ def process_server_response(
         if server_response.status == 200:
             # If the response is successful, overwrite local synapse state with
             # server's state only if the protocol allows mutation. To prevent overwrites,
-            # the protocol must set allow_mutation = False
+            # the protocol must set Frozen = True
             server_synapse = local_synapse.__class__(**json_response)
-            for key in local_synapse.dict().keys():
+            for key in local_synapse.model_dump().keys():
                 try:
                     # Set the attribute in the local synapse from the corresponding
                     # attribute in the server synapse
@@ -715,16 +715,16 @@ def process_server_response(
         # Merge dendrite headers
         local_synapse.dendrite.__dict__.update(
             {
-                **local_synapse.dendrite.dict(exclude_none=True),  # type: ignore
-                **server_headers.dendrite.dict(exclude_none=True),  # type: ignore
+                **local_synapse.dendrite.model_dump(exclude_none=True),  # type: ignore
+                **server_headers.dendrite.model_dump(exclude_none=True),  # type: ignore
             }
         )
 
         # Merge axon headers
         local_synapse.axon.__dict__.update(
             {
-                **local_synapse.axon.dict(exclude_none=True),  # type: ignore
-                **server_headers.axon.dict(exclude_none=True),  # type: ignore
+                **local_synapse.axon.model_dump(exclude_none=True),  # type: ignore
+                **server_headers.axon.model_dump(exclude_none=True),  # type: ignore
             }
         )
 
@@ -806,3 +806,24 @@ def __del__(self):
             del dendrite  # This will implicitly invoke the __del__ method and close the session.
         """
         self.close_session()
+
+
+# For back-compatibility with torch
+BaseModel: Union["torch.nn.Module", object] = torch.nn.Module if use_torch() else object
+
+
+class dendrite(DendriteMixin, BaseModel):  # type: ignore
+    def __init__(
+        self, wallet: Optional[Union[bittensor.wallet, bittensor.Keypair]] = None
+    ):
+        if use_torch():
+            torch.nn.Module.__init__(self)
+        DendriteMixin.__init__(self, wallet)
+
+
+if not use_torch():
+
+    async def call(self, *args, **kwargs):
+        return await self.forward(*args, **kwargs)
+
+    dendrite.__call__ = call
diff --git a/bittensor/extrinsics/delegation.py b/bittensor/extrinsics/delegation.py
index f568f63998..66accfc983 100644
--- a/bittensor/extrinsics/delegation.py
+++ b/bittensor/extrinsics/delegation.py
@@ -75,7 +75,7 @@ def nominate_extrinsic(
                 )
                 bittensor.logging.success(
                     prefix="Become Delegate",
-                    sufix="<green>Finalized: </green>" + str(success),
+                    suffix="<green>Finalized: </green>" + str(success),
                 )
 
             # Raises NominationError if False
@@ -86,14 +86,14 @@ def nominate_extrinsic(
                 ":cross_mark: [red]Failed[/red]: error:{}".format(e)
             )
             bittensor.logging.warning(
-                prefix="Set weights", sufix="<red>Failed: </red>" + str(e)
+                prefix="Set weights", suffix="<red>Failed: </red>" + str(e)
             )
         except NominationError as e:
             bittensor.__console__.print(
                 ":cross_mark: [red]Failed[/red]: error:{}".format(e)
             )
             bittensor.logging.warning(
-                prefix="Set weights", sufix="<red>Failed: </red>" + str(e)
+                prefix="Set weights", suffix="<red>Failed: </red>" + str(e)
             )
 
     return False
@@ -414,14 +414,7 @@ def decrease_take_extrinsic(
 
             return success
 
-        except Exception as e:
-            bittensor.__console__.print(
-                ":cross_mark: [red]Failed[/red]: error:{}".format(e)
-            )
-            bittensor.logging.warning(
-                prefix="Set weights", sufix="<red>Failed: </red>" + str(e)
-            )
-        except TakeError as e:
+        except (TakeError, Exception) as e:
             bittensor.__console__.print(
                 ":cross_mark: [red]Failed[/red]: error:{}".format(e)
             )
diff --git a/bittensor/extrinsics/prometheus.py b/bittensor/extrinsics/prometheus.py
index 5a28507ee5..350817e11f 100644
--- a/bittensor/extrinsics/prometheus.py
+++ b/bittensor/extrinsics/prometheus.py
@@ -63,7 +63,7 @@ def prometheus_extrinsic(
                 )
             )
             bittensor.logging.success(
-                prefix="External IP", sufix="<blue>{}</blue>".format(external_ip)
+                prefix="External IP", suffix="<blue>{}</blue>".format(external_ip)
             )
         except Exception as E:
             raise RuntimeError(
diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py
index 664ac5ea69..879214ad92 100644
--- a/bittensor/extrinsics/registration.py
+++ b/bittensor/extrinsics/registration.py
@@ -17,12 +17,15 @@
 # DEALINGS IN THE SOFTWARE.
 
 import bittensor
-
-import torch
 import time
 from rich.prompt import Confirm
 from typing import List, Union, Optional, Tuple
-from bittensor.utils.registration import POWSolution, create_pow
+from bittensor.utils.registration import (
+    POWSolution,
+    create_pow,
+    torch,
+    log_no_torch_error,
+)
 
 
 def register_extrinsic(
@@ -102,6 +105,10 @@ def register_extrinsic(
         ):
             return False
 
+    if not torch:
+        log_no_torch_error()
+        return False
+
     # Attempt rolling registration.
     attempts = 1
     while True:
@@ -112,7 +119,7 @@ def register_extrinsic(
         if cuda:
             if not torch.cuda.is_available():
                 if prompt:
-                    bittensor.__console__.error("CUDA is not available.")
+                    bittensor.__console__.print("CUDA is not available.")
                 return False
             pow_result: Optional[POWSolution] = create_pow(
                 subtensor,
@@ -340,7 +347,7 @@ def run_faucet_extrinsic(
     num_processes: Optional[int] = None,
     update_interval: Optional[int] = None,
     log_verbose: bool = False,
-) -> bool:
+) -> Tuple[bool, str]:
     r"""Runs a continual POW to get a faucet of TAO on the test net.
 
     Args:
@@ -377,7 +384,11 @@ def run_faucet_extrinsic(
                 subtensor.network,
             )
         ):
-            return False
+            return False, ""
+
+    if not torch:
+        log_no_torch_error()
+        return False, "Requires torch"
 
     # Unlock coldkey
     wallet.coldkey
@@ -391,13 +402,13 @@ def run_faucet_extrinsic(
     while True:
         try:
             pow_result = None
-            while pow_result == None or pow_result.is_stale(subtensor=subtensor):
+            while pow_result is None or pow_result.is_stale(subtensor=subtensor):
                 # Solve latest POW.
                 if cuda:
                     if not torch.cuda.is_available():
                         if prompt:
-                            bittensor.__console__.error("CUDA is not available.")
-                        return False
+                            bittensor.__console__.print("CUDA is not available.")
+                        return False, "CUDA is not available."
                     pow_result: Optional[POWSolution] = create_pow(
                         subtensor,
                         wallet,
diff --git a/bittensor/extrinsics/root.py b/bittensor/extrinsics/root.py
index 5c98eebe8b..a8a4819fb1 100644
--- a/bittensor/extrinsics/root.py
+++ b/bittensor/extrinsics/root.py
@@ -19,12 +19,14 @@
 import bittensor
 
 import time
-import torch
 import logging
+import numpy as np
+from numpy.typing import NDArray
 from rich.prompt import Confirm
-from typing import Union
+from typing import Union, List
 import bittensor.utils.weight_utils as weight_utils
 from bittensor.btlogging.defines import BITTENSOR_LOGGER_NAME
+from bittensor.utils.registration import torch, legacy_torch_api_compat
 
 logger = logging.getLogger(BITTENSOR_LOGGER_NAME)
 
@@ -98,11 +100,12 @@ def root_register_extrinsic(
                 )
 
 
+@legacy_torch_api_compat
 def set_root_weights_extrinsic(
     subtensor: "bittensor.subtensor",
     wallet: "bittensor.wallet",
-    netuids: Union[torch.LongTensor, list],
-    weights: Union[torch.FloatTensor, list],
+    netuids: Union[NDArray[np.int64], "torch.LongTensor", List[int]],
+    weights: Union[NDArray[np.float32], "torch.FloatTensor", List[float]],
     version_key: int = 0,
     wait_for_inclusion: bool = False,
     wait_for_finalization: bool = False,
@@ -113,9 +116,9 @@ def set_root_weights_extrinsic(
     Args:
         wallet (bittensor.wallet):
             Bittensor wallet object.
-        netuids (List[int]):
+        netuids (Union[NDArray[np.int64], torch.LongTensor, List[int]]):
             The ``netuid`` of the subnet to set weights for.
-        weights ( Union[torch.FloatTensor, list]):
+        weights (Union[NDArray[np.float32], torch.FloatTensor, list]):
             Weights to set. These must be ``float`` s and must correspond to the passed ``netuid`` s.
         version_key (int):
             The version key of the validator.
@@ -132,22 +135,22 @@ def set_root_weights_extrinsic(
 
     # First convert types.
     if isinstance(netuids, list):
-        netuids = torch.tensor(netuids, dtype=torch.int64)
+        netuids = np.array(netuids, dtype=np.int64)
     if isinstance(weights, list):
-        weights = torch.tensor(weights, dtype=torch.float32)
+        weights = np.array(weights, dtype=np.float32)
 
     # Get weight restrictions.
     min_allowed_weights = subtensor.min_allowed_weights(netuid=0)
     max_weight_limit = subtensor.max_weight_limit(netuid=0)
 
     # Get non zero values.
-    non_zero_weight_idx = torch.argwhere(weights > 0).squeeze(dim=1)
+    non_zero_weight_idx = np.argwhere(weights > 0).squeeze(axis=1)
     non_zero_weight_uids = netuids[non_zero_weight_idx]
     non_zero_weights = weights[non_zero_weight_idx]
-    if non_zero_weights.numel() < min_allowed_weights:
+    if non_zero_weights.size < min_allowed_weights:
         raise ValueError(
             "The minimum number of weights required to set weights is {}, got {}".format(
-                min_allowed_weights, non_zero_weights.numel()
+                min_allowed_weights, non_zero_weights.size
             )
         )
 
@@ -192,13 +195,13 @@ def set_root_weights_extrinsic(
             if not wait_for_finalization and not wait_for_inclusion:
                 return True
 
-            if success == True:
+            if success is True:
                 bittensor.__console__.print(
                     ":white_heavy_check_mark: [green]Finalized[/green]"
                 )
                 bittensor.logging.success(
                     prefix="Set weights",
-                    sufix="<green>Finalized: </green>" + str(success),
+                    suffix="<green>Finalized: </green>" + str(success),
                 )
                 return True
             else:
@@ -207,7 +210,7 @@ def set_root_weights_extrinsic(
                 )
                 bittensor.logging.warning(
                     prefix="Set weights",
-                    sufix="<red>Failed: </red>" + str(error_message),
+                    suffix="<red>Failed: </red>" + str(error_message),
                 )
                 return False
 
@@ -217,6 +220,6 @@ def set_root_weights_extrinsic(
                 ":cross_mark: [red]Failed[/red]: error:{}".format(e)
             )
             bittensor.logging.warning(
-                prefix="Set weights", sufix="<red>Failed: </red>" + str(e)
+                prefix="Set weights", suffix="<red>Failed: </red>" + str(e)
             )
             return False
diff --git a/bittensor/extrinsics/serving.py b/bittensor/extrinsics/serving.py
index 4bd7277788..05bf4c4369 100644
--- a/bittensor/extrinsics/serving.py
+++ b/bittensor/extrinsics/serving.py
@@ -176,7 +176,7 @@ def serve_axon_extrinsic(
                 )
             )
             bittensor.logging.success(
-                prefix="External IP", sufix="<blue>{}</blue>".format(external_ip)
+                prefix="External IP", suffix="<blue>{}</blue>".format(external_ip)
             )
         except Exception as E:
             raise RuntimeError(
diff --git a/bittensor/extrinsics/set_weights.py b/bittensor/extrinsics/set_weights.py
index 5e0c7cc6af..5db0a1a7a9 100644
--- a/bittensor/extrinsics/set_weights.py
+++ b/bittensor/extrinsics/set_weights.py
@@ -18,12 +18,14 @@
 
 import bittensor
 
-import torch
 import logging
+import numpy as np
+from numpy.typing import NDArray
 from rich.prompt import Confirm
 from typing import Union, Tuple
 import bittensor.utils.weight_utils as weight_utils
 from bittensor.btlogging.defines import BITTENSOR_LOGGER_NAME
+from bittensor.utils.registration import torch, use_torch
 
 logger = logging.getLogger(BITTENSOR_LOGGER_NAME)
 
@@ -32,8 +34,8 @@ def set_weights_extrinsic(
     subtensor: "bittensor.subtensor",
     wallet: "bittensor.wallet",
     netuid: int,
-    uids: Union[torch.LongTensor, list],
-    weights: Union[torch.FloatTensor, list],
+    uids: Union[NDArray[np.int64], "torch.LongTensor", list],
+    weights: Union[NDArray[np.float32], "torch.FloatTensor", list],
     version_key: int = 0,
     wait_for_inclusion: bool = False,
     wait_for_finalization: bool = False,
@@ -48,9 +50,9 @@ def set_weights_extrinsic(
             Bittensor wallet object.
         netuid (int):
             The ``netuid`` of the subnet to set weights for.
-        uids (Union[torch.LongTensor, list]):
+        uids (Union[NDArray[np.int64], torch.LongTensor, list]):
             The ``uint64`` uids of destination neurons.
-        weights ( Union[torch.FloatTensor, list]):
+        weights (Union[NDArray[np.float32], torch.FloatTensor, list]):
             The weights to set. These must be ``float`` s and correspond to the passed ``uid`` s.
         version_key (int):
             The version key of the validator.
@@ -64,12 +66,17 @@ def set_weights_extrinsic(
         success (bool):
             Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``.
     """
-
     # First convert types.
-    if isinstance(uids, list):
-        uids = torch.tensor(uids, dtype=torch.int64)
-    if isinstance(weights, list):
-        weights = torch.tensor(weights, dtype=torch.float32)
+    if use_torch():
+        if isinstance(uids, list):
+            uids = torch.tensor(uids, dtype=torch.int64)
+        if isinstance(weights, list):
+            weights = torch.tensor(weights, dtype=torch.float32)
+    else:
+        if isinstance(uids, list):
+            uids = np.array(uids, dtype=np.int64)
+        if isinstance(weights, list):
+            weights = np.array(weights, dtype=np.float32)
 
     # Reformat and normalize.
     weight_uids, weight_vals = weight_utils.convert_weights_and_uids_for_emit(
@@ -108,7 +115,7 @@ def set_weights_extrinsic(
                 )
                 bittensor.logging.success(
                     prefix="Set weights",
-                    sufix="<green>Finalized: </green>" + str(success),
+                    suffix="<green>Finalized: </green>" + str(success),
                 )
                 return True, "Successfully set weights and Finalized."
             else:
@@ -117,16 +124,15 @@ def set_weights_extrinsic(
                 )
                 bittensor.logging.warning(
                     prefix="Set weights",
-                    sufix="<red>Failed: </red>" + str(error_message),
+                    suffix="<red>Failed: </red>" + str(error_message),
                 )
                 return False, error_message
 
         except Exception as e:
-            # TODO( devs ): lets remove all of the bittensor.__console__ calls and replace with loguru.
             bittensor.__console__.print(
                 ":cross_mark: [red]Failed[/red]: error:{}".format(e)
             )
             bittensor.logging.warning(
-                prefix="Set weights", sufix="<red>Failed: </red>" + str(e)
+                prefix="Set weights", suffix="<red>Failed: </red>" + str(e)
             )
             return False, str(e)
diff --git a/bittensor/keyfile.py b/bittensor/keyfile.py
index f0962c09a4..b5157cea4a 100644
--- a/bittensor/keyfile.py
+++ b/bittensor/keyfile.py
@@ -276,13 +276,12 @@ def get_coldkey_password_from_environment(coldkey_name: str) -> Optional[str]:
     Returns:
         password (str): The password retrieved from the environment variables, or ``None`` if not found.
     """
-    for env_var in os.environ:
-        if env_var.upper().startswith("BT_COLD_PW_") and env_var.upper().endswith(
-            coldkey_name.upper()
-        ):
-            return os.getenv(env_var)
-
-    return None
+    envs = {
+        normalized_env_name: env_value
+        for env_name, env_value in os.environ.items()
+        if (normalized_env_name := env_name.upper()).startswith("BT_COLD_PW_")
+    }
+    return envs.get(f"BT_COLD_PW_{coldkey_name.upper()}")
 
 
 def decrypt_keyfile_data(
diff --git a/bittensor/metagraph.py b/bittensor/metagraph.py
index 6159b84c24..8bad4d6c78 100644
--- a/bittensor/metagraph.py
+++ b/bittensor/metagraph.py
@@ -17,14 +17,37 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
+from abc import ABC, abstractmethod
 import os
-import torch
+import pickle
+import numpy as np
+from numpy.typing import NDArray
 import bittensor
 from os import listdir
 from os.path import join
-from typing import List, Optional
+from typing import List, Optional, Union, Tuple
 
 from bittensor.chain_data import AxonInfo
+from bittensor.utils.registration import torch, use_torch
+
+METAGRAPH_STATE_DICT_NDARRAY_KEYS = [
+    "version",
+    "n",
+    "block",
+    "stake",
+    "total_stake",
+    "ranks",
+    "trust",
+    "consensus",
+    "validator_trust",
+    "incentive",
+    "emission",
+    "dividends",
+    "active",
+    "last_update",
+    "validator_permit",
+    "uids",
+]
 
 
 def get_save_dir(network: str, netuid: int) -> str:
@@ -70,7 +93,7 @@ def latest_block_path(dir_path: str) -> str:
         return latest_file_full_path
 
 
-class metagraph(torch.nn.Module):
+class MetagraphMixin(ABC):
     """
     The metagraph class is a core component of the Bittensor network, representing the neural graph that forms the backbone of the decentralized machine learning system.
 
@@ -83,9 +106,9 @@ class metagraph(torch.nn.Module):
     Args:
         netuid (int): A unique identifier that distinguishes between different instances or versions of the Bittensor network.
         network (str): The name of the network, signifying specific configurations or iterations within the Bittensor ecosystem.
-        version (torch.nn.parameter.Parameter): The version number of the network, formatted for compatibility with PyTorch models, integral for tracking network updates.
-        n (torch.nn.Parameter): The total number of neurons in the network, reflecting its size and complexity.
-        block (torch.nn.Parameter): The current block number in the blockchain, crucial for synchronizing with the network's latest state.
+        version (NDArray): The version number of the network, integral for tracking network updates.
+        n (NDArray): The total number of neurons in the network, reflecting its size and complexity.
+        block (NDArray): The current block number in the blockchain, crucial for synchronizing with the network's latest state.
         stake: Represents the cryptocurrency staked by neurons, impacting their influence and earnings within the network.
         total_stake: The cumulative stake across all neurons.
         ranks: Neuron rankings as per the Yuma Consensus algorithm, influencing their incentive distribution and network authority.
@@ -127,8 +150,30 @@ class metagraph(torch.nn.Module):
             hotkeys = deepcopy(metagraph.hotkeys)
     """
 
+    netuid: int
+    network: str
+    version: Union["torch.nn.Parameter", Tuple[NDArray]]
+    n: Union["torch.nn.Parameter", NDArray]
+    block: Union["torch.nn.Parameter", NDArray]
+    stake: Union["torch.nn.Parameter", NDArray]
+    total_stake: Union["torch.nn.Parameter", NDArray]
+    ranks: Union["torch.nn.Parameter", NDArray]
+    trust: Union["torch.nn.Parameter", NDArray]
+    consensus: Union["torch.nn.Parameter", NDArray]
+    validator_trust: Union["torch.nn.Parameter", NDArray]
+    incentive: Union["torch.nn.Parameter", NDArray]
+    emission: Union["torch.nn.Parameter", NDArray]
+    dividends: Union["torch.nn.Parameter", NDArray]
+    active: Union["torch.nn.Parameter", NDArray]
+    last_update: Union["torch.nn.Parameter", NDArray]
+    validator_permit: Union["torch.nn.Parameter", NDArray]
+    weights: Union["torch.nn.Parameter", NDArray]
+    bonds: Union["torch.nn.Parameter", NDArray]
+    uids: Union["torch.nn.Parameter", NDArray]
+    axons: List[AxonInfo]
+
     @property
-    def S(self) -> torch.nn.Parameter:
+    def S(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Represents the stake of each neuron in the Bittensor network. Stake is an important concept in the
         Bittensor ecosystem, signifying the amount of network weight (or “stake”) each neuron holds,
@@ -136,12 +181,12 @@ def S(self) -> torch.nn.Parameter:
         from the network, playing a crucial role in the distribution of incentives and decision-making processes.
 
         Returns:
-            torch.nn.Parameter: A tensor representing the stake of each neuron in the network. Higher values signify a greater stake held by the respective neuron.
+            NDArray: A tensor representing the stake of each neuron in the network. Higher values signify a greater stake held by the respective neuron.
         """
         return self.total_stake
 
     @property
-    def R(self) -> torch.nn.Parameter:
+    def R(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Contains the ranks of neurons in the Bittensor network. Ranks are determined by the network based
         on each neuron's performance and contributions. Higher ranks typically indicate a greater level of
@@ -149,12 +194,12 @@ def R(self) -> torch.nn.Parameter:
         incentives within the network, with higher-ranked neurons receiving more incentive.
 
         Returns:
-            torch.nn.Parameter: A tensor where each element represents the rank of a neuron. Higher values indicate higher ranks within the network.
+            NDArray: A tensor where each element represents the rank of a neuron. Higher values indicate higher ranks within the network.
         """
         return self.ranks
 
     @property
-    def I(self) -> torch.nn.Parameter:
+    def I(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Incentive values of neurons represent the rewards they receive for their contributions to the network.
         The Bittensor network employs an incentive mechanism that rewards neurons based on their
@@ -162,12 +207,12 @@ def I(self) -> torch.nn.Parameter:
         trusted contributions are incentivized.
 
         Returns:
-            torch.nn.Parameter: A tensor of incentive values, indicating the rewards or benefits accrued by each neuron based on their contributions and network consensus.
+            NDArray: A tensor of incentive values, indicating the rewards or benefits accrued by each neuron based on their contributions and network consensus.
         """
         return self.incentive
 
     @property
-    def E(self) -> torch.nn.Parameter:
+    def E(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Denotes the emission values of neurons in the Bittensor network. Emissions refer to the distribution or
         release of rewards (often in the form of cryptocurrency) to neurons, typically based on their stake and
@@ -175,12 +220,12 @@ def E(self) -> torch.nn.Parameter:
         contributing neurons are appropriately rewarded.
 
         Returns:
-            torch.nn.Parameter: A tensor where each element represents the emission value for a neuron, indicating the amount of reward distributed to that neuron.
+            NDArray: A tensor where each element represents the emission value for a neuron, indicating the amount of reward distributed to that neuron.
         """
         return self.emission
 
     @property
-    def C(self) -> torch.nn.Parameter:
+    def C(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Represents the consensus values of neurons in the Bittensor network. Consensus is a measure of how
         much a neuron's contributions are trusted and agreed upon by the majority of the network. It is
@@ -189,13 +234,13 @@ def C(self) -> torch.nn.Parameter:
         are more widely trusted and valued across the network.
 
         Returns:
-            torch.nn.Parameter: A tensor of consensus values, where each element reflects the level of trust and agreement a neuron has achieved within the network.
+            NDArray: A tensor of consensus values, where each element reflects the level of trust and agreement a neuron has achieved within the network.
 
         """
         return self.consensus
 
     @property
-    def T(self) -> torch.nn.Parameter:
+    def T(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Represents the trust values assigned to each neuron in the Bittensor network. Trust is a key metric that
         reflects the reliability and reputation of a neuron based on its past behavior and contributions. It is
@@ -206,12 +251,12 @@ def T(self) -> torch.nn.Parameter:
         has in others. A higher value in the trust matrix suggests a stronger trust relationship between neurons.
 
         Returns:
-            torch.nn.Parameter: A tensor of trust values, where each element represents the trust level of a neuron. Higher values denote a higher level of trust within the network.
+            NDArray: A tensor of trust values, where each element represents the trust level of a neuron. Higher values denote a higher level of trust within the network.
         """
         return self.trust
 
     @property
-    def Tv(self) -> torch.nn.Parameter:
+    def Tv(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Contains the validator trust values of neurons in the Bittensor network. Validator trust is specifically
         associated with neurons that act as validators within the network. This specialized form of trust reflects
@@ -222,24 +267,24 @@ def Tv(self) -> torch.nn.Parameter:
         determining the validators' influence and responsibilities in these critical functions.
 
         Returns:
-            torch.nn.Parameter: A tensor of validator trust values, specifically applicable to neurons serving as validators, where higher values denote greater trustworthiness in their validation roles.
+            NDArray: A tensor of validator trust values, specifically applicable to neurons serving as validators, where higher values denote greater trustworthiness in their validation roles.
         """
         return self.validator_trust
 
     @property
-    def D(self) -> torch.nn.Parameter:
+    def D(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Represents the dividends received by neurons in the Bittensor network. Dividends are a form of reward or
         distribution, typically given to neurons based on their stake, performance, and contribution to the network.
         They are an integral part of the network's incentive structure, encouraging active and beneficial participation.
 
         Returns:
-            torch.nn.Parameter: A tensor of dividend values, where each element indicates the dividends received by a neuron, reflecting their share of network rewards.
+            NDArray: A tensor of dividend values, where each element indicates the dividends received by a neuron, reflecting their share of network rewards.
         """
         return self.dividends
 
     @property
-    def B(self) -> torch.nn.Parameter:
+    def B(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Bonds in the Bittensor network represent a speculative reward mechanism where neurons can accumulate
         bonds in other neurons. Bonds are akin to investments or stakes in other neurons, reflecting a belief in
@@ -247,12 +292,12 @@ def B(self) -> torch.nn.Parameter:
         among neurons while providing an additional layer of incentive.
 
         Returns:
-            torch.nn.Parameter: A tensor representing the bonds held by each neuron, where each value signifies the proportion of bonds owned by one neuron in another.
+            NDArray: A tensor representing the bonds held by each neuron, where each value signifies the proportion of bonds owned by one neuron in another.
         """
         return self.bonds
 
     @property
-    def W(self) -> torch.nn.Parameter:
+    def W(self) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Represents the weights assigned to each neuron in the Bittensor network. In the context of Bittensor,
         weights are crucial for determining the influence and interaction between neurons. Each neuron is responsible
@@ -265,7 +310,7 @@ def W(self) -> torch.nn.Parameter:
         placed on that neuron's contributions.
 
         Returns:
-            torch.nn.Parameter: A tensor of inter-peer weights, where each element :math:`w_{ij}` represents the weight assigned by neuron :math:`i` to neuron :math:`j`. This matrix is fundamental to the network's functioning, influencing the distribution of incentives and the inter-neuronal dynamics.
+            NDArray: A tensor of inter-peer weights, where each element :math:`w_{ij}` represents the weight assigned by neuron :math:`i` to neuron :math:`j`. This matrix is fundamental to the network's functioning, influencing the distribution of incentives and the inter-neuronal dynamics.
         """
         return self.weights
 
@@ -320,6 +365,25 @@ def addresses(self) -> List[str]:
         """
         return [axon.ip_str() for axon in self.axons]
 
+    @abstractmethod
+    def __init__(
+        self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True
+    ):
+        """
+        Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the provided arguments.
+        This method is the entry point for creating a metagraph object,
+        which is a central component in representing the state of the Bittensor network.
+        Args:
+            netuid (int): The unique identifier for the network, distinguishing this instance of the metagraph within potentially multiple network configurations.
+            network (str): The name of the network, which can indicate specific configurations or versions of the Bittensor network.
+            lite (bool): A flag indicating whether to use a lite version of the metagraph. The lite version may contain less detailed information but can be quicker to initialize and sync.
+            sync (bool): A flag indicating whether to synchronize the metagraph with the network upon initialization. Synchronization involves updating the metagraph's parameters to reflect the current state of the network.
+        Example:
+            Initializing a metagraph object for the Bittensor network with a specific network UID::
+                metagraph = metagraph(netuid=123, network="finney", lite=True, sync=True)
+        """
+        pass
+
     def __str__(self) -> str:
         """
         Provides a human-readable string representation of the metagraph object. This representation includes key identifiers and attributes of the metagraph, making it easier to quickly understand
@@ -380,87 +444,30 @@ def metadata(self) -> dict:
             "version": bittensor.__version__,
         }
 
-    def __init__(
-        self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True
-    ):
-        """
-        Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the provided arguments.
-
-        This method is the entry point for creating a metagraph object,
-        which is a central component in representing the state of the Bittensor network.
-
-        Args:
-            netuid (int): The unique identifier for the network, distinguishing this instance of the metagraph within potentially multiple network configurations.
-            network (str): The name of the network, which can indicate specific configurations or versions of the Bittensor network.
-            lite (bool): A flag indicating whether to use a lite version of the metagraph. The lite version may contain less detailed information but can be quicker to initialize and sync.
-            sync (bool): A flag indicating whether to synchronize the metagraph with the network upon initialization. Synchronization involves updating the metagraph's parameters to reflect the current state of the network.
-
-        Example:
-            Initializing a metagraph object for the Bittensor network with a specific network UID::
-
-                metagraph = metagraph(netuid=123, network="finney", lite=True, sync=True)
-        """
-        super(metagraph, self).__init__()
-        self.netuid = netuid
-        self.network = network
-        self.version = torch.nn.Parameter(
-            torch.tensor([bittensor.__version_as_int__], dtype=torch.int64),
-            requires_grad=False,
-        )
-        self.n: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([0], dtype=torch.int64), requires_grad=False
-        )
-        self.block: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([0], dtype=torch.int64), requires_grad=False
-        )
-        self.stake = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.total_stake: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.ranks: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.trust: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.consensus: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.validator_trust: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.incentive: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.emission: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.dividends: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.active = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.int64), requires_grad=False
-        )
-        self.last_update = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.int64), requires_grad=False
-        )
-        self.validator_permit = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.bool), requires_grad=False
-        )
-        self.weights: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.float32), requires_grad=False
-        )
-        self.bonds: torch.nn.Parameter = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.int64), requires_grad=False
-        )
-        self.uids = torch.nn.Parameter(
-            torch.tensor([], dtype=torch.int64), requires_grad=False
-        )
-        self.axons: List[AxonInfo] = []
-        if sync:
-            self.sync(block=None, lite=lite)
+    def state_dict(self):
+        return {
+            "netuid": self.netuid,
+            "network": self.network,
+            "version": self.version,
+            "n": self.n,
+            "block": self.block,
+            "stake": self.stake,
+            "total_stake": self.total_stake,
+            "ranks": self.ranks,
+            "trust": self.trust,
+            "consensus": self.consensus,
+            "validator_trust": self.validator_trust,
+            "incentive": self.incentive,
+            "emission": self.emission,
+            "dividends": self.dividends,
+            "active": self.active,
+            "last_update": self.last_update,
+            "validator_permit": self.validator_permit,
+            "weights": self.weights,
+            "bonds": self.bonds,
+            "uids": self.uids,
+            "axons": self.axons,
+        }
 
     def sync(
         self,
@@ -572,78 +579,14 @@ def _assign_neurons(self, block, lite, subtensor):
             self.neurons = subtensor.neurons(block=block, netuid=self.netuid)
         self.lite = lite
 
-    def _set_metagraph_attributes(self, block, subtensor):
-        """
-        Sets various attributes of the metagraph based on the latest network data fetched from the subtensor.
-
-        This method updates parameters like the number of neurons, block number, stakes, trusts, ranks, and other neuron-specific information.
-
-        Args:
-            block: The block number for which the metagraph attributes need to be set. If ``None``, the latest block data is used.
-            subtensor: The subtensor instance used for fetching the latest network data.
-
-        Internal Usage:
-            Used internally during the sync process to update the metagraph's attributes::
-
-                self._set_metagraph_attributes(block, subtensor)
-        """
-        # TODO: Check and test the setting of each attribute
-        self.n = self._create_tensor(len(self.neurons), dtype=torch.int64)
-        self.version = self._create_tensor(
-            [bittensor.__version_as_int__], dtype=torch.int64
-        )
-        self.block = self._create_tensor(
-            block if block else subtensor.block, dtype=torch.int64
-        )
-        self.uids = self._create_tensor(
-            [neuron.uid for neuron in self.neurons], dtype=torch.int64
-        )
-        self.trust = self._create_tensor(
-            [neuron.trust for neuron in self.neurons], dtype=torch.float32
-        )
-        self.consensus = self._create_tensor(
-            [neuron.consensus for neuron in self.neurons], dtype=torch.float32
-        )
-        self.incentive = self._create_tensor(
-            [neuron.incentive for neuron in self.neurons], dtype=torch.float32
-        )
-        self.dividends = self._create_tensor(
-            [neuron.dividends for neuron in self.neurons], dtype=torch.float32
-        )
-        self.ranks = self._create_tensor(
-            [neuron.rank for neuron in self.neurons], dtype=torch.float32
-        )
-        self.emission = self._create_tensor(
-            [neuron.emission for neuron in self.neurons], dtype=torch.float32
-        )
-        self.active = self._create_tensor(
-            [neuron.active for neuron in self.neurons], dtype=torch.int64
-        )
-        self.last_update = self._create_tensor(
-            [neuron.last_update for neuron in self.neurons], dtype=torch.int64
-        )
-        self.validator_permit = self._create_tensor(
-            [neuron.validator_permit for neuron in self.neurons], dtype=torch.bool
-        )
-        self.validator_trust = self._create_tensor(
-            [neuron.validator_trust for neuron in self.neurons], dtype=torch.float32
-        )
-        self.total_stake = self._create_tensor(
-            [neuron.total_stake.tao for neuron in self.neurons], dtype=torch.float32
-        )
-        self.stake = self._create_tensor(
-            [neuron.stake for neuron in self.neurons], dtype=torch.float32
-        )
-        self.axons = [n.axon_info for n in self.neurons]
-
-    def _create_tensor(self, data, dtype) -> torch.nn.Parameter:
+    @staticmethod
+    def _create_tensor(data, dtype) -> Union[NDArray, "torch.nn.Parameter"]:
         """
-        Creates a tensor parameter with the given data and data type. This method is a utility function used internally to encapsulate data into a PyTorch tensor, making it compatible with the metagraph's PyTorch
-        model structure.
+        Creates a numpy array with the given data and data type. This method is a utility function used internally to encapsulate data into a np.array, making it compatible with the metagraph's numpy model structure.
 
         Args:
             data: The data to be included in the tensor. This could be any numeric data, like stakes, ranks, etc.
-            dtype: The data type for the tensor, typically a PyTorch data type like ``torch.float32`` or ``torch.int64``.
+            dtype: The data type for the tensor, typically a numpy data type like ``np.float32`` or ``np.int64``.
 
         Returns:
             A tensor parameter encapsulating the provided data.
@@ -651,10 +594,14 @@ def _create_tensor(self, data, dtype) -> torch.nn.Parameter:
         Internal Usage:
             Used internally to create tensor parameters for various metagraph attributes::
 
-                self.stake = self._create_tensor(neuron_stakes, dtype=torch.float32)
+                self.stake = self._create_tensor(neuron_stakes, dtype=np.float32)
         """
         # TODO: Check and test the creation of tensor
-        return torch.nn.Parameter(torch.tensor(data, dtype=dtype), requires_grad=False)
+        return (
+            torch.nn.Parameter(torch.tensor(data, dtype=dtype), requires_grad=False)
+            if use_torch()
+            else np.array(data, dtype=dtype)
+        )
 
     def _set_weights_and_bonds(self, subtensor: Optional[bittensor.subtensor] = None):
         """
@@ -681,7 +628,9 @@ def _set_weights_and_bonds(self, subtensor: Optional[bittensor.subtensor] = None
                 [neuron.bonds for neuron in self.neurons], "bonds"
             )
 
-    def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter:
+    def _process_weights_or_bonds(
+        self, data, attribute: str
+    ) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Processes the raw weights or bonds data and converts it into a structured tensor format. This method handles the transformation of neuron connection data (``weights`` or ``bonds``) from a list or other unstructured format into a tensor that can be utilized within the metagraph model.
 
@@ -700,26 +649,39 @@ def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter:
         data_array = []
         for item in data:
             if len(item) == 0:
-                data_array.append(torch.zeros(len(self.neurons)))
+                if use_torch():
+                    data_array.append(torch.zeros(len(self.neurons)))  # type: ignore
+                else:
+                    data_array.append(np.zeros(len(self.neurons), dtype=np.float32))  # type: ignore
             else:
                 uids, values = zip(*item)
                 # TODO: Validate and test the conversion of uids and values to tensor
                 if attribute == "weights":
                     data_array.append(
                         bittensor.utils.weight_utils.convert_weight_uids_and_vals_to_tensor(
-                            len(self.neurons), list(uids), list(values)
+                            len(self.neurons), list(uids), list(values)  # type: ignore
                         )
                     )
                 else:
                     data_array.append(
-                        bittensor.utils.weight_utils.convert_bond_uids_and_vals_to_tensor(
+                        bittensor.utils.weight_utils.convert_bond_uids_and_vals_to_tensor(  # type: ignore
                             len(self.neurons), list(uids), list(values)
+                        ).astype(
+                            np.float32
                         )
                     )
-        tensor_param = (
-            torch.nn.Parameter(torch.stack(data_array), requires_grad=False)
-            if len(data_array)
-            else torch.nn.Parameter()
+        tensor_param: Union["torch.nn.Parameter", NDArray] = (
+            (
+                torch.nn.Parameter(torch.stack(data_array), requires_grad=False)
+                if len(data_array)
+                else torch.nn.Parameter()
+            )
+            if use_torch()
+            else (
+                np.stack(data_array)
+                if len(data_array)
+                else np.array([], dtype=np.float32)
+            )
         )
         if len(data_array) == 0:
             bittensor.logging.warning(
@@ -727,9 +689,13 @@ def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter:
             )
         return tensor_param
 
+    @abstractmethod
+    def _set_metagraph_attributes(self, block, subtensor):
+        pass
+
     def _process_root_weights(
         self, data, attribute: str, subtensor: bittensor.subtensor
-    ) -> torch.nn.Parameter:
+    ) -> Union[NDArray, "torch.nn.Parameter"]:
         """
         Specifically processes the root weights data for the metagraph. This method is similar to :func:`_process_weights_or_bonds` but is tailored for processing root weights, which have a different structure and significance in the network.
 
@@ -754,20 +720,31 @@ def _process_root_weights(
         subnets = subtensor.get_subnets()
         for item in data:
             if len(item) == 0:
-                data_array.append(torch.zeros(n_subnets))
+                if use_torch():
+                    data_array.append(torch.zeros(n_subnets))
+                else:
+                    data_array.append(np.zeros(n_subnets, dtype=np.float32))  # type: ignore
             else:
                 uids, values = zip(*item)
                 # TODO: Validate and test the conversion of uids and values to tensor
                 data_array.append(
-                    bittensor.utils.weight_utils.convert_root_weight_uids_and_vals_to_tensor(
+                    bittensor.utils.weight_utils.convert_root_weight_uids_and_vals_to_tensor(  # type: ignore
                         n_subnets, list(uids), list(values), subnets
                     )
                 )
 
-        tensor_param = (
-            torch.nn.Parameter(torch.stack(data_array), requires_grad=False)
-            if len(data_array)
-            else torch.nn.Parameter()
+        tensor_param: Union[NDArray, "torch.nn.Parameter"] = (
+            (
+                torch.nn.Parameter(torch.stack(data_array), requires_grad=False)
+                if len(data_array)
+                else torch.nn.Parameter()
+            )
+            if use_torch()
+            else (
+                np.stack(data_array)
+                if len(data_array)
+                else np.array([], dtype=np.float32)
+            )
         )
         if len(data_array) == 0:
             bittensor.logging.warning(
@@ -775,7 +752,7 @@ def _process_root_weights(
             )
         return tensor_param
 
-    def save(self) -> "metagraph":
+    def save(self) -> "metagraph":  # type: ignore
         """
         Saves the current state of the metagraph to a file on disk. This function is crucial for persisting the current state of the network's metagraph, which can later be reloaded or analyzed. The save operation includes all neuron attributes and parameters, ensuring a complete snapshot of the metagraph's state.
 
@@ -799,11 +776,19 @@ def save(self) -> "metagraph":
         """
         save_directory = get_save_dir(self.network, self.netuid)
         os.makedirs(save_directory, exist_ok=True)
-        graph_file = save_directory + f"/block-{self.block.item()}.pt"
-        state_dict = self.state_dict()
-        state_dict["axons"] = self.axons
-        torch.save(state_dict, graph_file)
-        state_dict = torch.load(graph_file)
+        if use_torch():
+            graph_filename = f"{save_directory}/block-{self.block.item()}.pt"
+            state_dict = self.state_dict()
+            state_dict["axons"] = self.axons
+            torch.save(state_dict, graph_filename)
+            state_dict = torch.load(
+                graph_filename
+            )  # verifies that the file can be loaded correctly
+        else:
+            graph_filename = f"{save_directory}/block-{self.block.item()}.pt"
+            state_dict = self.state_dict()
+            with open(graph_filename, "wb") as graph_file:
+                pickle.dump(state_dict, graph_file)
         return self
 
     def load(self):
@@ -831,7 +816,8 @@ def load(self):
         """
         self.load_from_path(get_save_dir(self.network, self.netuid))
 
-    def load_from_path(self, dir_path: str) -> "metagraph":
+    @abstractmethod
+    def load_from_path(self, dir_path: str) -> "metagraph":  # type: ignore
         """
         Loads the state of the metagraph from a specified directory path. This method is crucial for restoring the metagraph to a specific state based on saved data. It locates the latest block file in the given
         directory and loads all metagraph parameters from it. This is particularly useful for analyses that require historical states of the network or for restoring previous states of the metagraph in different
@@ -858,6 +844,156 @@ def load_from_path(self, dir_path: str) -> "metagraph":
             contain valid data for the metagraph. It is essential to ensure that the directory path and the
             state files within it are accurate and consistent with the expected metagraph structure.
         """
+        pass
+
+
+BaseClass: Union["torch.nn.Module", object] = torch.nn.Module if use_torch() else object
+
+
+class TorchMetaGraph(MetagraphMixin, BaseClass):  # type: ignore
+    def __init__(
+        self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True
+    ):
+        """
+        Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the provided arguments.
+        This method is the entry point for creating a metagraph object,
+        which is a central component in representing the state of the Bittensor network.
+        Args:
+            netuid (int): The unique identifier for the network, distinguishing this instance of the metagraph within potentially multiple network configurations.
+            network (str): The name of the network, which can indicate specific configurations or versions of the Bittensor network.
+            lite (bool): A flag indicating whether to use a lite version of the metagraph. The lite version may contain less detailed information but can be quicker to initialize and sync.
+            sync (bool): A flag indicating whether to synchronize the metagraph with the network upon initialization. Synchronization involves updating the metagraph's parameters to reflect the current state of the network.
+        Example:
+            Initializing a metagraph object for the Bittensor network with a specific network UID::
+                metagraph = metagraph(netuid=123, network="finney", lite=True, sync=True)
+        """
+        torch.nn.Module.__init__(self)
+        MetagraphMixin.__init__(self, netuid, network, lite, sync)
+        self.netuid = netuid
+        self.network = network
+        self.version = torch.nn.Parameter(
+            torch.tensor([bittensor.__version_as_int__], dtype=torch.int64),
+            requires_grad=False,
+        )
+        self.n: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([0], dtype=torch.int64), requires_grad=False
+        )
+        self.block: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([0], dtype=torch.int64), requires_grad=False
+        )
+        self.stake = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.total_stake: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.ranks: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.trust: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.consensus: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.validator_trust: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.incentive: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.emission: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.dividends: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.active = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.int64), requires_grad=False
+        )
+        self.last_update = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.int64), requires_grad=False
+        )
+        self.validator_permit = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.bool), requires_grad=False
+        )
+        self.weights: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.float32), requires_grad=False
+        )
+        self.bonds: torch.nn.Parameter = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.int64), requires_grad=False
+        )
+        self.uids = torch.nn.Parameter(
+            torch.tensor([], dtype=torch.int64), requires_grad=False
+        )
+        self.axons: List[AxonInfo] = []
+        if sync:
+            self.sync(block=None, lite=lite)
+
+    def _set_metagraph_attributes(self, block, subtensor):
+        """
+        Sets various attributes of the metagraph based on the latest network data fetched from the subtensor.
+
+        This method updates parameters like the number of neurons, block number, stakes, trusts, ranks, and other neuron-specific information.
+
+        Args:
+            block: The block number for which the metagraph attributes need to be set. If ``None``, the latest block data is used.
+            subtensor: The subtensor instance used for fetching the latest network data.
+
+        Internal Usage:
+            Used internally during the sync process to update the metagraph's attributes::
+
+                self._set_metagraph_attributes(block, subtensor)
+        """
+        self.n = self._create_tensor(len(self.neurons), dtype=torch.int64)
+        self.version = self._create_tensor(
+            [bittensor.__version_as_int__], dtype=torch.int64
+        )
+        self.block = self._create_tensor(
+            block if block else subtensor.block, dtype=torch.int64
+        )
+        self.uids = self._create_tensor(
+            [neuron.uid for neuron in self.neurons], dtype=torch.int64
+        )
+        self.trust = self._create_tensor(
+            [neuron.trust for neuron in self.neurons], dtype=torch.float32
+        )
+        self.consensus = self._create_tensor(
+            [neuron.consensus for neuron in self.neurons], dtype=torch.float32
+        )
+        self.incentive = self._create_tensor(
+            [neuron.incentive for neuron in self.neurons], dtype=torch.float32
+        )
+        self.dividends = self._create_tensor(
+            [neuron.dividends for neuron in self.neurons], dtype=torch.float32
+        )
+        self.ranks = self._create_tensor(
+            [neuron.rank for neuron in self.neurons], dtype=torch.float32
+        )
+        self.emission = self._create_tensor(
+            [neuron.emission for neuron in self.neurons], dtype=torch.float32
+        )
+        self.active = self._create_tensor(
+            [neuron.active for neuron in self.neurons], dtype=torch.int64
+        )
+        self.last_update = self._create_tensor(
+            [neuron.last_update for neuron in self.neurons], dtype=torch.int64
+        )
+        self.validator_permit = self._create_tensor(
+            [neuron.validator_permit for neuron in self.neurons], dtype=torch.bool
+        )
+        self.validator_trust = self._create_tensor(
+            [neuron.validator_trust for neuron in self.neurons], dtype=torch.float32
+        )
+        self.total_stake = self._create_tensor(
+            [neuron.total_stake.tao for neuron in self.neurons], dtype=torch.float32
+        )
+        self.stake = self._create_tensor(
+            [neuron.stake for neuron in self.neurons], dtype=torch.float32
+        )
+        self.axons = [n.axon_info for n in self.neurons]
+
+    def load_from_path(self, dir_path: str) -> "metagraph":  # type: ignore
         graph_file = latest_block_path(dir_path)
         state_dict = torch.load(graph_file)
         self.n = torch.nn.Parameter(state_dict["n"], requires_grad=False)
@@ -898,3 +1034,148 @@ def load_from_path(self, dir_path: str) -> "metagraph":
         if "bonds" in state_dict:
             self.bonds = torch.nn.Parameter(state_dict["bonds"], requires_grad=False)
         return self
+
+
+class NonTorchMetagraph(MetagraphMixin):
+    def __init__(
+        self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True
+    ):
+        # super(metagraph, self).__init__()
+        MetagraphMixin.__init__(self, netuid, network, lite, sync)
+
+        self.netuid = netuid
+        self.network = network
+        self.version = (np.array([bittensor.__version_as_int__], dtype=np.int64),)
+        self.n = np.array([0], dtype=np.int64)
+        self.block = np.array([0], dtype=np.int64)
+        self.stake = np.array([], dtype=np.float32)
+        self.total_stake = np.array([], dtype=np.float32)
+        self.ranks = np.array([], dtype=np.float32)
+        self.trust = np.array([], dtype=np.float32)
+        self.consensus = np.array([], dtype=np.float32)
+        self.validator_trust = np.array([], dtype=np.float32)
+        self.incentive = np.array([], dtype=np.float32)
+        self.emission = np.array([], dtype=np.float32)
+        self.dividends = np.array([], dtype=np.float32)
+        self.active = np.array([], dtype=np.int64)
+        self.last_update = np.array([], dtype=np.int64)
+        self.validator_permit = np.array([], dtype=bool)
+        self.weights = np.array([], dtype=np.float32)
+        self.bonds = np.array([], dtype=np.int64)
+        self.uids = np.array([], dtype=np.int64)
+        self.axons: List[AxonInfo] = []
+        if sync:
+            self.sync(block=None, lite=lite)
+
+    def _set_metagraph_attributes(self, block, subtensor):
+        """
+        Sets various attributes of the metagraph based on the latest network data fetched from the subtensor.
+
+        This method updates parameters like the number of neurons, block number, stakes, trusts, ranks, and other neuron-specific information.
+
+        Args:
+            block: The block number for which the metagraph attributes need to be set. If ``None``, the latest block data is used.
+            subtensor: The subtensor instance used for fetching the latest network data.
+
+        Internal Usage:
+            Used internally during the sync process to update the metagraph's attributes::
+
+                self._set_metagraph_attributes(block, subtensor)
+        """
+        # TODO: Check and test the setting of each attribute
+        self.n = self._create_tensor(len(self.neurons), dtype=np.int64)
+        self.version = self._create_tensor(
+            [bittensor.__version_as_int__], dtype=np.int64
+        )
+        self.block = self._create_tensor(
+            block if block else subtensor.block, dtype=np.int64
+        )
+        self.uids = self._create_tensor(
+            [neuron.uid for neuron in self.neurons], dtype=np.int64
+        )
+        self.trust = self._create_tensor(
+            [neuron.trust for neuron in self.neurons], dtype=np.float32
+        )
+        self.consensus = self._create_tensor(
+            [neuron.consensus for neuron in self.neurons], dtype=np.float32
+        )
+        self.incentive = self._create_tensor(
+            [neuron.incentive for neuron in self.neurons], dtype=np.float32
+        )
+        self.dividends = self._create_tensor(
+            [neuron.dividends for neuron in self.neurons], dtype=np.float32
+        )
+        self.ranks = self._create_tensor(
+            [neuron.rank for neuron in self.neurons], dtype=np.float32
+        )
+        self.emission = self._create_tensor(
+            [neuron.emission for neuron in self.neurons], dtype=np.float32
+        )
+        self.active = self._create_tensor(
+            [neuron.active for neuron in self.neurons], dtype=np.int64
+        )
+        self.last_update = self._create_tensor(
+            [neuron.last_update for neuron in self.neurons], dtype=np.int64
+        )
+        self.validator_permit = self._create_tensor(
+            [neuron.validator_permit for neuron in self.neurons], dtype=bool
+        )
+        self.validator_trust = self._create_tensor(
+            [neuron.validator_trust for neuron in self.neurons], dtype=np.float32
+        )
+        self.total_stake = self._create_tensor(
+            [neuron.total_stake.tao for neuron in self.neurons], dtype=np.float32
+        )
+        self.stake = self._create_tensor(
+            [neuron.stake for neuron in self.neurons], dtype=np.float32
+        )
+        self.axons = [n.axon_info for n in self.neurons]
+
+    def load_from_path(self, dir_path: str) -> "metagraph":  # type: ignore
+        graph_filename = latest_block_path(dir_path)
+        try:
+            with open(graph_filename, "rb") as graph_file:
+                state_dict = pickle.load(graph_file)
+        except pickle.UnpicklingError:
+            bittensor.__console__.print(
+                "Unable to load file. Attempting to restore metagraph using torch."
+            )
+            bittensor.__console__.print(
+                ":warning:[yellow]Warning:[/yellow] This functionality exists to load "
+                "metagraph state from legacy saves, but will not be supported in the future."
+            )
+            try:
+                import torch as real_torch
+
+                state_dict = real_torch.load(graph_filename)
+                for key in METAGRAPH_STATE_DICT_NDARRAY_KEYS:
+                    state_dict[key] = state_dict[key].detach().numpy()
+                del real_torch
+            except (RuntimeError, ImportError):
+                bittensor.__console__.print("Unable to load file. It may be corrupted.")
+                raise
+
+        self.n = state_dict["n"]
+        self.block = state_dict["block"]
+        self.uids = state_dict["uids"]
+        self.stake = state_dict["stake"]
+        self.total_stake = state_dict["total_stake"]
+        self.ranks = state_dict["ranks"]
+        self.trust = state_dict["trust"]
+        self.consensus = state_dict["consensus"]
+        self.validator_trust = state_dict["validator_trust"]
+        self.incentive = state_dict["incentive"]
+        self.emission = state_dict["emission"]
+        self.dividends = state_dict["dividends"]
+        self.active = state_dict["active"]
+        self.last_update = state_dict["last_update"]
+        self.validator_permit = state_dict["validator_permit"]
+        self.axons = state_dict["axons"]
+        if "weights" in state_dict:
+            self.weights = state_dict["weights"]
+        if "bonds" in state_dict:
+            self.bonds = state_dict["bonds"]
+        return self
+
+
+metagraph = TorchMetaGraph if use_torch() else NonTorchMetagraph
diff --git a/bittensor/stream.py b/bittensor/stream.py
index 1a7d17aaed..e0dc17c42c 100644
--- a/bittensor/stream.py
+++ b/bittensor/stream.py
@@ -4,7 +4,7 @@
 from starlette.responses import StreamingResponse as _StreamingResponse
 from starlette.types import Send, Receive, Scope
 from typing import Callable, Awaitable
-from pydantic import BaseModel
+from pydantic import ConfigDict, BaseModel
 from abc import ABC, abstractmethod
 
 
@@ -36,8 +36,7 @@ class StreamingSynapse(bittensor.Synapse, ABC):
     and extract JSON data. It also includes a method to create a streaming response object.
     """
 
-    class Config:
-        validate_assignment = True
+    model_config = ConfigDict(validate_assignment=True)
 
     class BTStreamingResponse(_StreamingResponse):
         """
diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py
index 26c1c52bbd..701e469e19 100644
--- a/bittensor/subtensor.py
+++ b/bittensor/subtensor.py
@@ -16,24 +16,32 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-import os
+"""
+The ``bittensor.subtensor`` module in Bittensor serves as a crucial interface for interacting with the Bittensor
+blockchain, facilitating a range of operations essential for the decentralized machine learning network.
+"""
+import argparse
 import copy
+import functools
+import socket
 import time
-import torch
-import logging
-import argparse
-import bittensor
-import scalecodec
+from typing import List, Dict, Union, Optional, Tuple, TypedDict, Any, TypeVar
 
+import numpy as np
+import scalecodec
+from numpy.typing import NDArray
 from retry import retry
-from typing import List, Dict, Union, Optional, Tuple, TypedDict, Any, TypeVar
-from substrateinterface.base import QueryMapResult, SubstrateInterface, ExtrinsicReceipt
-from substrateinterface.exceptions import SubstrateRequestException
 from scalecodec.base import RuntimeConfiguration
+from scalecodec.exceptions import RemainingScaleBytesNotEmptyException
 from scalecodec.type_registry import load_type_registry_preset
 from scalecodec.types import GenericCall
+from substrateinterface.base import QueryMapResult, SubstrateInterface, ExtrinsicReceipt
+from substrateinterface.exceptions import SubstrateRequestException
+
 
-# Local imports.
+import bittensor
+from bittensor.utils import torch
+from bittensor.btlogging import logging as _logger
 from .chain_data import (
     NeuronInfo,
     DelegateInfo,
@@ -48,51 +56,70 @@
     custom_rpc_type_registry,
 )
 from .errors import IdentityError, NominationError, StakeError, TakeError
+from .extrinsics.delegation import (
+    delegate_extrinsic,
+    nominate_extrinsic,
+    undelegate_extrinsic,
+    increase_take_extrinsic,
+    decrease_take_extrinsic,
+)
 from .extrinsics.network import (
     register_subnetwork_extrinsic,
     set_hyperparameter_extrinsic,
 )
-from .extrinsics.staking import add_stake_extrinsic, add_stake_multiple_extrinsic
-from .extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic
-from .extrinsics.serving import (
-    serve_extrinsic,
-    serve_axon_extrinsic,
-    publish_metadata,
-    get_metadata,
-)
+from .extrinsics.prometheus import prometheus_extrinsic
 from .extrinsics.registration import (
     register_extrinsic,
     burned_register_extrinsic,
     run_faucet_extrinsic,
     swap_hotkey_extrinsic,
 )
-from .extrinsics.transfer import transfer_extrinsic
-from .extrinsics.set_weights import set_weights_extrinsic
-from .extrinsics.prometheus import prometheus_extrinsic
-from .extrinsics.delegation import (
-    delegate_extrinsic,
-    nominate_extrinsic,
-    undelegate_extrinsic,
-    increase_take_extrinsic,
-    decrease_take_extrinsic,
-)
+from .extrinsics.root import root_register_extrinsic, set_root_weights_extrinsic
 from .extrinsics.senate import (
     register_senate_extrinsic,
     leave_senate_extrinsic,
     vote_senate_extrinsic,
 )
-from .extrinsics.root import root_register_extrinsic, set_root_weights_extrinsic
+from .extrinsics.serving import (
+    serve_extrinsic,
+    serve_axon_extrinsic,
+    publish_metadata,
+    get_metadata,
+)
+from .extrinsics.set_weights import set_weights_extrinsic
+from .extrinsics.staking import add_stake_extrinsic, add_stake_multiple_extrinsic
+from .extrinsics.transfer import transfer_extrinsic
+from .extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic
 from .types import AxonServeCallParams, PrometheusServeCallParams
-from .utils import U16_NORMALIZED_FLOAT, ss58_to_vec_u8, U64_NORMALIZED_FLOAT
+from .utils import (
+    U16_NORMALIZED_FLOAT,
+    ss58_to_vec_u8,
+    U64_NORMALIZED_FLOAT,
+    networking,
+)
 from .utils.balance import Balance
 from .utils.registration import POWSolution
+from .utils.subtensor import get_subtensor_errors
+from .utils.registration import legacy_torch_api_compat
 
-logger = logging.getLogger("subtensor")
 
 KEY_NONCE: Dict[str, int] = {}
 
 T = TypeVar("T")
 
+#######
+# Monkey patch in caching the convert_type_string method
+#######
+if hasattr(RuntimeConfiguration, "convert_type_string"):
+    original_convert_type_string = RuntimeConfiguration.convert_type_string
+
+    @functools.lru_cache(maxsize=None)
+    def convert_type_string(cls, name):
+        return original_convert_type_string(name)
+
+    RuntimeConfiguration.convert_type_string = convert_type_string
+#######
+
 
 class ParamWithTypes(TypedDict):
     name: str  # Name of the parameter.
@@ -101,10 +128,11 @@ class ParamWithTypes(TypedDict):
 
 class subtensor:
     """
-    The Subtensor class in Bittensor serves as a crucial interface for interacting with the Bittensor blockchain, facilitating a range of operations essential for the decentralized machine learning network.
+    The Subtensor class in Bittensor serves as a crucial interface for interacting with the Bittensor blockchain,
+    facilitating a range of operations essential for the decentralized machine learning network.
 
-    This class enables neurons (network participants) to engage in activities such as registering on the network, managing
-    staked weights, setting inter-neuronal weights, and participating in consensus mechanisms.
+    This class enables neurons (network participants) to engage in activities such as registering on the network,
+    managing staked weights, setting inter-neuronal weights, and participating in consensus mechanisms.
 
     The Bittensor network operates on a digital ledger where each neuron holds stakes (S) and learns a set
     of inter-peer weights (W). These weights, set by the neurons themselves, play a critical role in determining
@@ -122,8 +150,10 @@ class subtensor:
     investments.
 
     Args:
-        network (str): The name of the Bittensor network (e.g., 'finney', 'test', 'archive', 'local') the instance is connected to, determining the blockchain interaction context.
-        chain_endpoint (str): The blockchain node endpoint URL, enabling direct communication with the Bittensor blockchain for transaction processing and data retrieval.
+        network (str): The name of the Bittensor network (e.g., 'finney', 'test', 'archive', 'local') the instance is
+            connected to, determining the blockchain interaction context.
+        chain_endpoint (str): The blockchain node endpoint URL, enabling direct communication with the Bittensor
+            blockchain for transaction processing and data retrieval.
 
     Example Usage::
 
@@ -151,18 +181,144 @@ class subtensor:
 
     By facilitating these operations, the Subtensor class is instrumental in maintaining the decentralized
     intelligence and dynamic learning environment of the Bittensor network, as envisioned in its foundational
-    principles and mechanisms described in the `NeurIPS paper <https://bittensor.com/pdfs/academia/NeurIPS_DAO_Workshop_2022_3_3.pdf>`_. paper.
+    principles and mechanisms described in the `NeurIPS paper
+    <https://bittensor.com/pdfs/academia/NeurIPS_DAO_Workshop_2022_3_3.pdf>`_. paper.
     """
 
+    def __init__(
+        self,
+        network: Optional[str] = None,
+        config: Optional[bittensor.config] = None,
+        _mock: bool = False,
+        log_verbose: bool = True,
+    ) -> None:
+        """
+        Initializes a Subtensor interface for interacting with the Bittensor blockchain.
+
+        NOTE:
+            Currently subtensor defaults to the ``finney`` network. This will change in a future release.
+
+        We strongly encourage users to run their own local subtensor node whenever possible. This increases
+        decentralization and resilience of the network. In a future release, local subtensor will become the
+        default and the fallback to ``finney`` removed. Please plan ahead for this change. We will provide detailed
+        instructions on how to run a local subtensor node in the documentation in a subsequent release.
+
+        Args:
+            network (str, optional): The network name to connect to (e.g., ``finney``, ``local``). This can also be the
+                chain endpoint (e.g., ``wss://entrypoint-finney.opentensor.ai:443``) and will be correctly parsed into
+                the network and chain endpoint. If not specified, defaults to the main Bittensor network.
+            config (bittensor.config, optional): Configuration object for the subtensor. If not provided, a default
+                configuration is used.
+            _mock (bool, optional): If set to ``True``, uses a mocked connection for testing purposes.
+
+        This initialization sets up the connection to the specified Bittensor network, allowing for various
+        blockchain operations such as neuron registration, stake management, and setting weights.
+
+        """
+        # Determine config.subtensor.chain_endpoint and config.subtensor.network config.
+        # If chain_endpoint is set, we override the network flag, otherwise, the chain_endpoint is assigned by the
+        # network.
+        # Argument importance: network > chain_endpoint > config.subtensor.chain_endpoint > config.subtensor.network
+
+        # Check if network is a config object. (Single argument passed as first positional)
+        if isinstance(network, bittensor.config):
+            if network.subtensor is None:
+                _logger.warning(
+                    "If passing a bittensor config object, it must not be empty. Using default subtensor config."
+                )
+                config = None
+            else:
+                config = network
+            network = None
+
+        if config is None:
+            config = subtensor.config()
+        self.config = copy.deepcopy(config)  # type: ignore
+
+        # Setup config.subtensor.network and config.subtensor.chain_endpoint
+        self.chain_endpoint, self.network = subtensor.setup_config(network, config)  # type: ignore
+
+        if (
+            self.network == "finney"
+            or self.chain_endpoint == bittensor.__finney_entrypoint__
+        ) and log_verbose:
+            _logger.info(
+                f"You are connecting to {self.network} network with endpoint {self.chain_endpoint}."
+            )
+            _logger.warning(
+                "We strongly encourage running a local subtensor node whenever possible. "
+                "This increases decentralization and resilience of the network."
+            )
+            _logger.warning(
+                "In a future release, local subtensor will become the default endpoint. "
+                "To get ahead of this change, please run a local subtensor node and point to it."
+            )
+
+        # Attempt to connect to chosen endpoint. Fallback to finney if local unavailable.
+        try:
+            # Set up params.
+            self.substrate = SubstrateInterface(
+                ss58_format=bittensor.__ss58_format__,
+                use_remote_preset=True,
+                url=self.chain_endpoint,
+                type_registry=bittensor.__type_registry__,
+            )
+        except ConnectionRefusedError as e:
+            _logger.error(
+                f"Could not connect to {self.network} network with {self.chain_endpoint} chain endpoint. Exiting...",
+            )
+            _logger.info(
+                "You can check if you have connectivity by running this command: nc -vz localhost "
+                f"{self.chain_endpoint.split(':')[2]}"
+            )
+            exit(1)
+            # TODO (edu/phil): Advise to run local subtensor and point to dev docs.
+
+        try:
+            self.substrate.websocket.settimeout(600)
+        # except:
+        #     bittensor.logging.warning("Could not set websocket timeout.")
+        except AttributeError as e:
+            _logger.warning(f"AttributeError: {e}")
+        except TypeError as e:
+            _logger.warning(f"TypeError: {e}")
+        except (socket.error, OSError) as e:
+            _logger.warning(f"Socket error: {e}")
+
+        if log_verbose:
+            _logger.info(
+                f"Connected to {self.network} network and {self.chain_endpoint}."
+            )
+
+        self._subtensor_errors: Dict[str, Dict[str, str]] = {}
+
+    def __str__(self) -> str:
+        if self.network == self.chain_endpoint:
+            # Connecting to chain endpoint without network known.
+            return "subtensor({})".format(self.chain_endpoint)
+        else:
+            # Connecting to network with endpoint known.
+            return "subtensor({}, {})".format(self.network, self.chain_endpoint)
+
+    def __repr__(self) -> str:
+        return self.__str__()
+
     @staticmethod
-    def config() -> "bittensor.config":
+    def config() -> bittensor.config:
+        """
+        Creates and returns a Bittensor configuration object.
+
+        Returns:
+            config (bittensor.config): A Bittensor configuration object configured with arguments added by the
+                `subtensor.add_args` method.
+        """
         parser = argparse.ArgumentParser()
         subtensor.add_args(parser)
         return bittensor.config(parser, args=[])
 
     @classmethod
     def help(cls):
-        """Print help to stdout"""
+        """Print help to stdout."""
         parser = argparse.ArgumentParser()
         cls.add_args(parser)
         print(cls.__new__.__doc__)
@@ -170,15 +326,31 @@ def help(cls):
 
     @classmethod
     def add_args(cls, parser: argparse.ArgumentParser, prefix: Optional[str] = None):
+        """
+        Adds command-line arguments to the provided ArgumentParser for configuring the Subtensor settings.
+
+        Args:
+            parser (argparse.ArgumentParser): The ArgumentParser object to which the Subtensor arguments will be added.
+            prefix (Optional[str]): An optional prefix for the argument names. If provided, the prefix is prepended to
+                each argument name.
+
+        Arguments added:
+            --subtensor.network: The Subtensor network flag. Possible values are 'finney', 'test', 'archive', and
+                'local'. Overrides the chain endpoint if set.
+            --subtensor.chain_endpoint: The Subtensor chain endpoint flag. If set, it overrides the network flag.
+            --subtensor._mock: If true, uses a mocked connection to the chain.
+
+        Example:
+            parser = argparse.ArgumentParser()
+            Subtensor.add_args(parser)
+        """
         prefix_str = "" if prefix is None else f"{prefix}."
         try:
-            default_network = os.getenv("BT_SUBTENSOR_NETWORK") or "finney"
-            default_chain_endpoint = (
-                os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT")
-                or bittensor.__finney_entrypoint__
-            )
+            default_network = bittensor.__networks__[1]
+            default_chain_endpoint = bittensor.__finney_entrypoint__
+
             parser.add_argument(
-                "--" + prefix_str + "subtensor.network",
+                f"--{prefix_str}subtensor.network",
                 default=default_network,
                 type=str,
                 help="""The subtensor network flag. The likely choices are:
@@ -191,18 +363,16 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: Optional[str] = None)
                                     """,
             )
             parser.add_argument(
-                "--" + prefix_str + "subtensor.chain_endpoint",
+                f"--{prefix_str}subtensor.chain_endpoint",
                 default=default_chain_endpoint,
                 type=str,
-                help="""The subtensor endpoint flag. If set, overrides the --network flag.
-                                    """,
+                help="""The subtensor endpoint flag. If set, overrides the --network flag.""",
             )
             parser.add_argument(
-                "--" + prefix_str + "subtensor._mock",
+                f"--{prefix_str}subtensor._mock",
                 default=False,
                 type=bool,
-                help="""If true, uses a mocked connection to the chain.
-                                    """,
+                help="""If true, uses a mocked connection to the chain.""",
             )
 
         except argparse.ArgumentError:
@@ -214,8 +384,8 @@ def determine_chain_endpoint_and_network(network: str):
         """Determines the chain endpoint and network from the passed network or chain_endpoint.
 
         Args:
-            network (str): The network flag. The choices are: ``-- finney`` (main network), ``-- archive`` (archive network +300 blocks), ``-- local`` (local running network), ``-- test`` (test network).
-            chain_endpoint (str): The chain endpoint flag. If set, overrides the network argument.
+            network (str): The network flag. The choices are: ``-- finney`` (main network), ``-- archive``
+                (archive network +300 blocks), ``-- local`` (local running network), ``-- test`` (test network).
         Returns:
             network (str): The network flag.
             chain_endpoint (str): The chain endpoint flag. If set, overrides the ``network`` argument.
@@ -224,7 +394,7 @@ def determine_chain_endpoint_and_network(network: str):
             return None, None
         if network in ["finney", "local", "test", "archive"]:
             if network == "finney":
-                # Kiru Finney stagin network.
+                # Kiru Finney staging network.
                 return network, bittensor.__finney_entrypoint__
             elif network == "local":
                 return network, bittensor.__local_entrypoint__
@@ -255,7 +425,26 @@ def determine_chain_endpoint_and_network(network: str):
 
     @staticmethod
     def setup_config(network: str, config: bittensor.config):
-        if network != None:
+        """
+        Sets up and returns the configuration for the Subtensor network and endpoint.
+
+        This method determines the appropriate network and chain endpoint based on the provided network string or
+        configuration object. It evaluates the network and endpoint in the following order of precedence:
+        1. Provided network string.
+        2. Configured chain endpoint in the `config` object.
+        3. Configured network in the `config` object.
+        4. Default chain endpoint.
+        5. Default network.
+
+        Args:
+            network (str): The name of the Subtensor network. If None, the network and endpoint will be determined from
+                the `config` object.
+            config (bittensor.config): The configuration object containing the network and chain endpoint settings.
+
+        Returns:
+            tuple: A tuple containing the formatted WebSocket endpoint URL and the evaluated network name.
+        """
+        if network is not None:
             (
                 evaluated_network,
                 evaluated_endpoint,
@@ -302,150 +491,20 @@ def setup_config(network: str, config: bittensor.config):
                 )
 
         return (
-            bittensor.utils.networking.get_formatted_ws_endpoint_url(
-                evaluated_endpoint
-            ),
+            networking.get_formatted_ws_endpoint_url(evaluated_endpoint),
             evaluated_network,
         )
 
-    def __init__(
-        self,
-        network: Optional[str] = None,
-        config: Optional[bittensor.config] = None,
-        _mock: bool = False,
-        log_verbose: bool = True,
-    ) -> None:
-        """
-        Initializes a Subtensor interface for interacting with the Bittensor blockchain.
-
-        NOTE:
-            Currently subtensor defaults to the ``finney`` network. This will change in a future release.
-
-        We strongly encourage users to run their own local subtensor node whenever possible. This increases
-        decentralization and resilience of the network. In a future release, local subtensor will become the
-        default and the fallback to ``finney`` removed. Please plan ahead for this change. We will provide detailed
-        instructions on how to run a local subtensor node in the documentation in a subsequent release.
-
-        Args:
-            network (str, optional): The network name to connect to (e.g., ``finney``, ``local``). This can also be the chain endpoint (e.g., ``wss://entrypoint-finney.opentensor.ai:443``) and will be correctly parsed into the network and chain endpoint. If not specified, defaults to the main Bittensor network.
-            config (bittensor.config, optional): Configuration object for the subtensor. If not provided, a default configuration is used.
-            _mock (bool, optional): If set to ``True``, uses a mocked connection for testing purposes.
-
-        This initialization sets up the connection to the specified Bittensor network, allowing for various
-        blockchain operations such as neuron registration, stake management, and setting weights.
-
-        """
-        # Determine config.subtensor.chain_endpoint and config.subtensor.network config.
-        # If chain_endpoint is set, we override the network flag, otherwise, the chain_endpoint is assigned by the network.
-        # Argument importance: network > chain_endpoint > config.subtensor.chain_endpoint > config.subtensor.network
-
-        # Check if network is a config object. (Single argument passed as first positional)
-        if isinstance(network, bittensor.config):
-            if network.subtensor is None:
-                bittensor.logging.warning(
-                    "If passing a bittensor config object, it must not be empty. Using default subtensor config."
-                )
-                config = None
-            else:
-                config = network
-            network = None
-
-        if config is None:
-            config = subtensor.config()
-        self.config = copy.deepcopy(config)  # type: ignore
-
-        # Setup config.subtensor.network and config.subtensor.chain_endpoint
-        self.chain_endpoint, self.network = subtensor.setup_config(network, config)  # type: ignore
-
-        if (
-            self.network == "finney"
-            or self.chain_endpoint == bittensor.__finney_entrypoint__
-        ) and log_verbose:
-            bittensor.logging.info(
-                f"You are connecting to {self.network} network with endpoint {self.chain_endpoint}."
-            )
-            bittensor.logging.warning(
-                "We strongly encourage running a local subtensor node whenever possible. "
-                "This increases decentralization and resilience of the network."
-            )
-            bittensor.logging.warning(
-                "In a future release, local subtensor will become the default endpoint. "
-                "To get ahead of this change, please run a local subtensor node and point to it."
-            )
-
-        # Returns a mocked connection with a background chain connection.
-        self.config.subtensor._mock = (
-            _mock
-            if _mock != None
-            else self.config.subtensor.get("_mock", bittensor.defaults.subtensor._mock)
-        )
-        if (
-            self.config.subtensor._mock
-        ):  # TODO: review this doesn't appear to be used anywhere.
-            config.subtensor._mock = True
-            return bittensor.MockSubtensor()  # type: ignore
-
-        # Attempt to connect to chosen endpoint. Fallback to finney if local unavailable.
-        try:
-            # Set up params.
-            self.substrate = SubstrateInterface(
-                ss58_format=bittensor.__ss58_format__,
-                use_remote_preset=True,
-                url=self.chain_endpoint,
-                type_registry=bittensor.__type_registry__,
-            )
-        except ConnectionRefusedError as e:
-            bittensor.logging.error(
-                f"Could not connect to {self.network} network with {self.chain_endpoint} chain endpoint. Exiting..."
-            )
-            bittensor.logging.info(
-                f"You can check if you have connectivity by runing this command: nc -vz localhost {self.chain_endpoint.split(':')[2]}"
-            )
-            exit(1)
-            # TODO (edu/phil): Advise to run local subtensor and point to dev docs.
-
-        try:
-            self.substrate.websocket.settimeout(600)
-        except:
-            bittensor.logging.warning("Could not set websocket timeout.")
-
-        if log_verbose:
-            bittensor.logging.info(
-                f"Connected to {self.network} network and {self.chain_endpoint}."
-            )
-
-    def __str__(self) -> str:
-        if self.network == self.chain_endpoint:
-            # Connecting to chain endpoint without network known.
-            return "subtensor({})".format(self.chain_endpoint)
-        else:
-            # Connecting to network with endpoint known.
-            return "subtensor({}, {})".format(self.network, self.chain_endpoint)
-
-    def __repr__(self) -> str:
-        return self.__str__()
-
-    ####################
-    #### SubstrateInterface related
-    ####################
-    def connect_websocket(self):
-        """
-        (Re)creates the websocket connection, if the URL contains a 'ws' or 'wss' scheme
-        """
-        self.subtensor.connect_websocket
-
     def close(self):
-        """
-        Cleans up resources for this subtensor instance like active websocket connection and active extensions
-        """
+        """Cleans up resources for this subtensor instance like active websocket connection and active extensions."""
         self.substrate.close()
 
-    #####################
-    #### Delegation #####
-    #####################
+    ##############
+    # Delegation #
+    ##############
     def nominate(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         wait_for_finalization: bool = False,
         wait_for_inclusion: bool = True,
     ) -> bool:
@@ -456,7 +515,8 @@ def nominate(
 
         Args:
             wallet (bittensor.wallet): The wallet containing the hotkey to be nominated.
-            wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the blockchain.
+            wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the
+                blockchain.
             wait_for_inclusion (bool, optional): If ``True``, waits until the transaction is included in a block.
 
         Returns:
@@ -474,7 +534,7 @@ def nominate(
 
     def delegate(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         delegate_ss58: Optional[str] = None,
         amount: Optional[Union[Balance, float]] = None,
         wait_for_inclusion: bool = True,
@@ -488,8 +548,12 @@ def delegate(
 
         Args:
             wallet (bittensor.wallet): The wallet containing the hotkey to be nominated.
-            wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the blockchain.
+            delegate_ss58 (Optional[str]): The ``SS58`` address of the delegate neuron.
+            amount (Union[Balance, float]): The amount of TAO to undelegate.
+            wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the
+                blockchain.
             wait_for_inclusion (bool, optional): If ``True``, waits until the transaction is included in a block.
+            prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding.
 
         Returns:
             bool: ``True`` if the nomination process is successful, False otherwise.
@@ -509,7 +573,7 @@ def delegate(
 
     def undelegate(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         delegate_ss58: Optional[str] = None,
         amount: Optional[Union[Balance, float]] = None,
         wait_for_inclusion: bool = True,
@@ -546,7 +610,7 @@ def undelegate(
 
     def set_take(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         delegate_ss58: Optional[str] = None,
         take: float = 0.0,
         wait_for_inclusion: bool = True,
@@ -558,10 +622,13 @@ def set_take(
             wallet (bittensor.wallet): The wallet containing the hotkey to be nominated.
             delegate_ss58 (str, optional): Hotkey
             take (float): Delegate take on subnet ID
-            wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the blockchain.
+            wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the
+                blockchain.
             wait_for_inclusion (bool, optional): If ``True``, waits until the transaction is included in a block.
+
         Returns:
             bool: ``True`` if the process is successful, False otherwise.
+
         This function is a key part of the decentralized governance mechanism of Bittensor, allowing for the
         dynamic selection and participation of validators in the network's consensus process.
         """
@@ -569,7 +636,7 @@ def set_take(
         if delegate_ss58 is None:
             raise ValueError("delegate_ss58 cannot be None")
 
-        # Caulate u16 representation of the take
+        # Calculate u16 representation of the take
         takeu16 = int(take * 0xFFFF)
 
         # Check if the new take is greater or lower than existing take or if existing is set
@@ -608,7 +675,7 @@ def set_take(
 
     def send_extrinsic(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         module: str,
         function: str,
         params: dict,
@@ -655,6 +722,7 @@ def send_extrinsic(
         old_init_runtime = self.substrate.init_runtime
         self.substrate.init_runtime = lambda: None
         self.substrate.init_runtime = old_init_runtime
+        response = None
 
         for attempt in range(1, max_retries + 1):
             try:
@@ -694,29 +762,28 @@ def send_extrinsic(
             except SubstrateRequestException as e:
                 if "Priority is too low" in e.args[0]["message"]:
                     wait = min(wait_time * attempt, max_wait)
-                    bittensor.logging.warning(
+                    _logger.warning(
                         f"Priority is too low, retrying with new nonce: {nonce} in {wait} seconds."
                     )
                     nonce = nonce + 1
                     time.sleep(wait)
                     continue
                 else:
-                    bittensor.logging.error(f"Error sending extrinsic: {e}")
+                    _logger.error(f"Error sending extrinsic: {e}")
                     response = None
 
         return response
 
-    #####################
-    #### Set Weights ####
-    #####################
+    ###############
+    # Set Weights #
+    ###############
     def set_weights(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         netuid: int,
-        uids: Union[torch.LongTensor, list],
-        weights: Union[torch.FloatTensor, list],
+        uids: Union[NDArray[np.int64], "torch.LongTensor", list],
+        weights: Union[NDArray[np.float32], "torch.FloatTensor", list],
         version_key: int = bittensor.__version_as_int__,
-        uid: Optional[int] = None,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = False,
         prompt: bool = False,
@@ -730,9 +797,8 @@ def set_weights(
         Args:
             wallet (bittensor.wallet): The wallet associated with the neuron setting the weights.
             netuid (int): The unique identifier of the subnet.
-            uid (int): Unique identifier for the caller on the subnet specified by `netuid`.
-            uids (Union[torch.LongTensor, list]): The list of neuron UIDs that the weights are being set for.
-            weights (Union[torch.FloatTensor, list]): The corresponding weights to be set for each UID.
+            uids (Union[NDArray[np.int64], torch.LongTensor, list]): The list of neuron UIDs that the weights are being set for.
+            weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The corresponding weights to be set for each UID.
             version_key (int, optional): Version key for compatibility with the network.
             wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block.
             wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain.
@@ -767,7 +833,7 @@ def set_weights(
                     prompt=prompt,
                 )
             except Exception as e:
-                bittensor.logging.error(f"Error setting weights: {e}")
+                _logger.error(f"Error setting weights: {e}")
             finally:
                 retries += 1
 
@@ -775,7 +841,7 @@ def set_weights(
 
     def _do_set_weights(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         uids: List[int],
         vals: List[int],
         netuid: int,
@@ -804,7 +870,7 @@ def _do_set_weights(
         trust in other neurons based on observed performance and contributions.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -829,7 +895,7 @@ def make_substrate_call_with_retry():
             )
             # We only wait here if we expect finalization.
             if not wait_for_finalization and not wait_for_inclusion:
-                return True, "Not waiting for finalziation or inclusion."
+                return True, "Not waiting for finalization or inclusion."
 
             response.process_events()
             if response.is_success:
@@ -839,12 +905,12 @@ def make_substrate_call_with_retry():
 
         return make_substrate_call_with_retry()
 
-    ######################
-    #### Registration ####
-    ######################
+    ################
+    # Registration #
+    ################
     def register(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         netuid: int,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
@@ -867,8 +933,19 @@ def register(
             wallet (bittensor.wallet): The wallet associated with the neuron to be registered.
             netuid (int): The unique identifier of the subnet.
             wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block.
+                Defaults to `False`.
             wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain.
-            Other arguments: Various optional parameters to customize the registration process.
+                 Defaults to `True`.
+            prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding.
+            max_allowed_attempts (int): Maximum number of attempts to register the wallet.
+            output_in_place (bool): If true, prints the progress of the proof of work to the console in-place. Meaning
+                the progress is printed on the same lines. Defaults to `True`.
+            cuda (bool): If ``true``, the wallet should be registered using CUDA device(s). Defaults to `False`.
+            dev_id (Union[List[int], int]): The CUDA device id to use, or a list of device ids. Defaults to `0` (zero).
+            tpb (int): The number of threads per block (CUDA). Default to `256`.
+            num_processes (Optional[int]): The number of processes to use to register. Default to `None`.
+            update_interval (Optional[int]): The number of nonces to solve between updates.  Default to `None`.
+            log_verbose (bool): If ``true``, the registration process will log more information.  Default to `False`.
 
         Returns:
             bool: ``True`` if the registration is successful, False otherwise.
@@ -895,13 +972,29 @@ def register(
 
     def swap_hotkey(
         self,
-        wallet: "bittensor.wallet",
-        new_wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
+        new_wallet: bittensor.wallet,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
         prompt: bool = False,
     ) -> bool:
-        """Swaps an old hotkey to a new hotkey."""
+        """
+        Swaps an old hotkey with a new hotkey for the specified wallet.
+
+        This method initiates an extrinsic to change the hotkey associated with a wallet to a new hotkey. It provides
+        options to wait for inclusion and finalization of the transaction, and to prompt the user for confirmation.
+
+        Args:
+            wallet (bittensor.wallet): The wallet whose hotkey is to be swapped.
+            new_wallet (bittensor.wallet): The new wallet with the hotkey to be set.
+            wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block.
+                Default is `False`.
+            wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is `True`.
+            prompt (bool): Whether to prompt the user for confirmation before proceeding. Default is `False`.
+
+        Returns:
+            bool: True if the hotkey swap was successful, False otherwise.
+        """
         return swap_hotkey_extrinsic(
             subtensor=self,
             wallet=wallet,
@@ -913,7 +1006,7 @@ def swap_hotkey(
 
     def run_faucet(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
         prompt: bool = False,
@@ -933,7 +1026,20 @@ def run_faucet(
 
         Args:
             wallet (bittensor.wallet): The wallet for which the faucet transaction is to be run.
-            Other arguments: Various optional parameters to customize the faucet transaction process.
+            wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block.
+                Defaults to `False`.
+            wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain.
+                 Defaults to `True`.
+            prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding.
+            max_allowed_attempts (int): Maximum number of attempts to register the wallet.
+            output_in_place (bool): If true, prints the progress of the proof of work to the console in-place. Meaning
+                the progress is printed on the same lines. Defaults to `True`.
+            cuda (bool): If ``true``, the wallet should be registered using CUDA device(s). Defaults to `False`.
+            dev_id (Union[List[int], int]): The CUDA device id to use, or a list of device ids. Defaults to `0` (zero).
+            tpb (int): The number of threads per block (CUDA). Default to `256`.
+            num_processes (Optional[int]): The number of processes to use to register. Default to `None`.
+            update_interval (Optional[int]): The number of nonces to solve between updates.  Default to `None`.
+            log_verbose (bool): If ``true``, the registration process will log more information.  Default to `False`.
 
         Returns:
             bool: ``True`` if the faucet transaction is successful, False otherwise.
@@ -942,9 +1048,10 @@ def run_faucet(
         the necessary resources to begin their journey in the decentralized AI network.
 
         Note:
-            This is for testnet ONLY and is disabled currently. You must build your own staging subtensor chain with the ``--features pow-faucet`` argument to enable this.
+            This is for testnet ONLY and is disabled currently. You must build your own staging subtensor chain with the
+            ``--features pow-faucet`` argument to enable this.
         """
-        return run_faucet_extrinsic(
+        result, _ = run_faucet_extrinsic(
             subtensor=self,
             wallet=wallet,
             wait_for_inclusion=wait_for_inclusion,
@@ -959,10 +1066,11 @@ def run_faucet(
             update_interval=update_interval,
             log_verbose=log_verbose,
         )
+        return result
 
     def burned_register(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         netuid: int,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
@@ -976,8 +1084,10 @@ def burned_register(
             wallet (bittensor.wallet): The wallet associated with the neuron to be registered.
             netuid (int): The unique identifier of the subnet.
             wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block.
+                Defaults to `False`.
             wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain.
-            prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding.
+                Defaults to `True`.
+            prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. Defaults to `False`.
 
         Returns:
             bool: ``True`` if the registration is successful, False otherwise.
@@ -994,7 +1104,7 @@ def burned_register(
     def _do_pow_register(
         self,
         netuid: int,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         pow_result: POWSolution,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
@@ -1005,14 +1115,17 @@ def _do_pow_register(
             netuid (int): The subnet to register on.
             wallet (bittensor.wallet): The wallet to register.
             pow_result (POWSolution): The PoW result to register.
-            wait_for_inclusion (bool): If ``true``, waits for the extrinsic to be included in a block.
-            wait_for_finalization (bool): If ``true``, waits for the extrinsic to be finalized.
+            wait_for_inclusion (bool): If ``True``, waits for the extrinsic to be included in a block.
+                Default to `False`.
+            wait_for_finalization (bool): If ``True``, waits for the extrinsic to be finalized. Default to `True`.
+
         Returns:
             success (bool): ``True`` if the extrinsic was included in a block.
-            error (Optional[str]): ``None`` on success or not waiting for inclusion/finalization, otherwise the error message.
+            error (Optional[str]): ``None`` on success or not waiting for inclusion/finalization, otherwise the error
+                message.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             # create extrinsic call
             call = self.substrate.compose_call(
@@ -1053,11 +1166,28 @@ def make_substrate_call_with_retry():
     def _do_burned_register(
         self,
         netuid: int,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
     ) -> Tuple[bool, Optional[str]]:
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        """
+        Performs a burned register extrinsic call to the Subtensor chain.
+
+        This method sends a registration transaction to the Subtensor blockchain using the burned register mechanism. It
+        retries the call up to three times with exponential backoff in case of failures.
+
+        Args:
+            netuid (int): The network unique identifier to register on.
+            wallet (bittensor.wallet): The wallet to be registered.
+            wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is False.
+            wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is True.
+
+        Returns:
+            Tuple[bool, Optional[str]]: A tuple containing a boolean indicating success or failure, and an optional
+                error message.
+        """
+
+        @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             # create extrinsic call
             call = self.substrate.compose_call(
@@ -1093,12 +1223,27 @@ def make_substrate_call_with_retry():
 
     def _do_swap_hotkey(
         self,
-        wallet: "bittensor.wallet",
-        new_wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
+        new_wallet: bittensor.wallet,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
     ) -> Tuple[bool, Optional[str]]:
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        """
+        Performs a hotkey swap extrinsic call to the Subtensor chain.
+
+        Args:
+            wallet (bittensor.wallet): The wallet whose hotkey is to be swapped.
+            new_wallet (bittensor.wallet): The wallet with the new hotkey to be set.
+            wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is
+            `False`.
+            wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is `True`.
+
+        Returns:
+            Tuple[bool, Optional[str]]: A tuple containing a boolean indicating success or failure, and an optional
+                error message.
+        """
+
+        @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             # create extrinsic call
             call = self.substrate.compose_call(
@@ -1132,12 +1277,12 @@ def make_substrate_call_with_retry():
 
         return make_substrate_call_with_retry()
 
-    ##################
-    #### Transfer ####
-    ##################
+    ############
+    # Transfer #
+    ############
     def transfer(
         self,
-        wallet: "bittensor.wallet",
+        wallet: bittensor.wallet,
         dest: str,
         amount: Union[Balance, float],
         wait_for_inclusion: bool = True,
@@ -1158,7 +1303,7 @@ def transfer(
             prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding.
 
         Returns:
-            bool: ``True`` if the transfer is successful, False otherwise.
+            transfer_extrinsic (bool): ``True`` if the transfer is successful, False otherwise.
 
         This function is essential for the fluid movement of tokens in the network, supporting
         various economic activities such as staking, delegation, and reward distribution.
@@ -1174,7 +1319,7 @@ def transfer(
         )
 
     def get_transfer_fee(
-        self, wallet: "bittensor.wallet", dest: str, value: Union[Balance, float, int]
+        self, wallet: bittensor.wallet, dest: str, value: Union[Balance, float, int]
     ) -> Balance:
         """
         Calculates the transaction fee for transferring tokens from a wallet to a specified destination address.
@@ -1184,7 +1329,8 @@ def get_transfer_fee(
         Args:
             wallet (bittensor.wallet): The wallet from which the transfer is initiated.
             dest (str): The ``SS58`` address of the destination account.
-            value (Union[Balance, float, int]): The amount of tokens to be transferred, specified as a Balance object, or in Tao (float) or Rao (int) units.
+            value (Union[Balance, float, int]): The amount of tokens to be transferred, specified as a Balance object,
+                or in Tao (float) or Rao (int) units.
 
         Returns:
             Balance: The estimated transaction fee for the transfer, represented as a Balance object.
@@ -1194,30 +1340,40 @@ def get_transfer_fee(
         provides a crucial tool for managing financial operations within the Bittensor network.
         """
         if isinstance(value, float):
-            transfer_balance = Balance.from_tao(value)
+            value = Balance.from_tao(value)
         elif isinstance(value, int):
-            transfer_balance = Balance.from_rao(value)
-
-        call = self.substrate.compose_call(
-            call_module="Balances",
-            call_function="transfer_allow_death",
-            call_params={"dest": dest, "value": transfer_balance.rao},
-        )
+            value = Balance.from_rao(value)
 
-        try:
-            payment_info = self.substrate.get_payment_info(
-                call=call, keypair=wallet.coldkeypub
+        if isinstance(value, Balance):
+            call = self.substrate.compose_call(
+                call_module="Balances",
+                call_function="transfer_allow_death",
+                call_params={"dest": dest, "value": value.rao},
             )
-        except Exception as e:
-            bittensor.__console__.print(
-                ":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n  {}[/bold white]".format(
-                    e
+
+            try:
+                payment_info = self.substrate.get_payment_info(
+                    call=call, keypair=wallet.coldkeypub
                 )
-            )
-            payment_info = {"partialFee": 2e7}  # assume  0.02 Tao
+            except Exception as e:
+                bittensor.__console__.print(
+                    ":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n  {}[/bold white]".format(
+                        e
+                    )
+                )
+                payment_info = {"partialFee": int(2e7)}  # assume  0.02 Tao
 
-        fee = Balance.from_rao(payment_info["partialFee"])
-        return fee
+            fee = Balance.from_rao(payment_info["partialFee"])
+            return fee
+        else:
+            fee = Balance.from_rao(int(2e7))
+            _logger.error(
+                "To calculate the transaction fee, the value must be Balance, float, or int. Received type: %s. Fee "
+                "is %s",
+                type(value),
+                2e7,
+            )
+            return fee
 
     def _do_transfer(
         self,
@@ -1241,7 +1397,7 @@ def _do_transfer(
             error (str): Error message if transfer failed.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="Balances",
@@ -1475,7 +1631,7 @@ def _do_serve_axon(
         enhancing the decentralized computation capabilities of Bittensor.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -1537,7 +1693,7 @@ def _do_serve_prometheus(
             error (:func:`Optional[str]`): Error message if serve prometheus failed, ``None`` otherwise.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -1586,7 +1742,7 @@ def _do_associate_ips(
             error (:func:`Optional[str]`): Error message if associate IPs failed, None otherwise.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -1715,7 +1871,7 @@ def _do_stake(
             StakeError: If the extrinsic failed.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -1841,7 +1997,7 @@ def _do_unstake(
             StakeError: If the extrinsic failed.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -2159,7 +2315,7 @@ def _do_root_register(
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = True,
     ) -> Tuple[bool, Optional[str]]:
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             # create extrinsic call
             call = self.substrate.compose_call(
@@ -2190,11 +2346,12 @@ def make_substrate_call_with_retry():
 
         return make_substrate_call_with_retry()
 
+    @legacy_torch_api_compat
     def root_set_weights(
         self,
         wallet: "bittensor.wallet",
-        netuids: Union[torch.LongTensor, list],
-        weights: Union[torch.FloatTensor, list],
+        netuids: Union[NDArray[np.int64], "torch.LongTensor", list],
+        weights: Union[NDArray[np.float32], "torch.FloatTensor", list],
         version_key: int = 0,
         wait_for_inclusion: bool = False,
         wait_for_finalization: bool = False,
@@ -2206,8 +2363,8 @@ def root_set_weights(
 
         Args:
             wallet (bittensor.wallet): The wallet associated with the neuron setting the weights.
-            netuids (Union[torch.LongTensor, list]): The list of neuron UIDs for which weights are being set.
-            weights (Union[torch.FloatTensor, list]): The corresponding weights to be set for each UID.
+            netuids (Union[NDArray[np.int64], torch.LongTensor, list]): The list of neuron UIDs for which weights are being set.
+            weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The corresponding weights to be set for each UID.
             version_key (int, optional): Version key for compatibility with the network.
             wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block.
             wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain.
@@ -2260,7 +2417,7 @@ def query_identity(
         network-specific details, providing insights into the neuron's role and status within the Bittensor network.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query(
                 module="Registry",
@@ -2310,7 +2467,7 @@ def update_identity(
         call_params = bittensor.utils.wallet_utils.create_identity_dict(**params)
         call_params["identified"] = identified
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="Registry",
@@ -2343,7 +2500,7 @@ def commit(self, wallet, netuid: int, data: str):
 
     def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> str:
         metagraph = self.metagraph(netuid)
-        hotkey = metagraph.hotkeys[uid]
+        hotkey = metagraph.hotkeys[uid]  # type: ignore
 
         metadata = get_metadata(self, netuid, hotkey, block)
         commitment = metadata["info"]["fields"][0]  # type: ignore
@@ -2379,7 +2536,7 @@ def query_subtensor(
         providing valuable insights into the state and dynamics of the Bittensor ecosystem.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query(
                 module="SubtensorModule",
@@ -2416,7 +2573,7 @@ def query_map_subtensor(
         relationships within the Bittensor ecosystem, such as inter-neuronal connections and stake distributions.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query_map(
                 module="SubtensorModule",
@@ -2450,7 +2607,7 @@ def query_constant(
         operational parameters.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.get_constant(
                 module_name=module_name,
@@ -2489,7 +2646,7 @@ def query_module(
         parts of the Bittensor blockchain, enhancing the understanding and analysis of the network's state and dynamics.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query(
                 module=module,
@@ -2528,7 +2685,7 @@ def query_map(
         modules, offering insights into the network's state and the relationships between its different components.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query_map(
                 module=module,
@@ -2563,7 +2720,7 @@ def state_call(
         useful for specific use cases where standard queries are insufficient.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             block_hash = None if block is None else self.substrate.get_block_hash(block)
             params = [method, data]
@@ -3066,7 +3223,7 @@ def get_axon_info(
         result = self.query_subtensor("Axons", block, [netuid, hotkey_ss58])
         if result is not None and hasattr(result, "value"):
             return AxonInfo(
-                ip=bittensor.utils.networking.int_to_ip(result.value["ip"]),
+                ip=networking.int_to_ip(result.value["ip"]),
                 ip_type=result.value["ip_type"],
                 port=result.value["port"],
                 protocol=result.value["protocol"],
@@ -3086,7 +3243,7 @@ def get_prometheus_info(
         result = self.query_subtensor("Prometheus", block, [netuid, hotkey_ss58])
         if result is not None:
             return PrometheusInfo(
-                ip=bittensor.utils.networking.int_to_ip(result.value["ip"]),
+                ip=networking.int_to_ip(result.value["ip"]),
                 ip_type=result.value["ip_type"],
                 port=result.value["port"],
                 version=result.value["version"],
@@ -3095,9 +3252,9 @@ def get_prometheus_info(
         else:
             return None
 
-    ###########################
-    #### Global Parameters ####
-    ###########################
+    #####################
+    # Global Parameters #
+    #####################
 
     @property
     def block(self) -> int:
@@ -3355,7 +3512,7 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> List[SubnetInfo]:
         the roles of different subnets, and their unique features.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             block_hash = None if block is None else self.substrate.get_block_hash(block)
             params = []
@@ -3392,7 +3549,7 @@ def get_subnet_info(
         subnet, including its governance, performance, and role within the broader network.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             block_hash = None if block is None else self.substrate.get_block_hash(block)
             params = [netuid]
@@ -3552,7 +3709,7 @@ def get_delegate_by_hotkey(
         the Bittensor network's consensus and governance structures.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry(encoded_hotkey: List[int]):
             block_hash = None if block == None else self.substrate.get_block_hash(block)
             params = [encoded_hotkey]
@@ -3574,8 +3731,9 @@ def make_substrate_call_with_retry(encoded_hotkey: List[int]):
 
     def get_delegates(self, block: Optional[int] = None) -> List[DelegateInfo]:
         """
-        Retrieves a list of all delegate neurons within the Bittensor network. This function provides an
-        overview of the neurons that are actively involved in the network's delegation system.
+        Retrieves a list of all delegate neurons within the Bittensor network. This function provides an overview of the neurons that are actively involved in the network's delegation system.
+
+        Analyzing the delegate population offers insights into the network's governance dynamics and the distribution of trust and responsibility among participating neurons.
 
         Args:
             block (Optional[int], optional): The blockchain block number for the query.
@@ -3583,16 +3741,14 @@ def get_delegates(self, block: Optional[int] = None) -> List[DelegateInfo]:
         Returns:
             List[DelegateInfo]: A list of DelegateInfo objects detailing each delegate's characteristics.
 
-        Analyzing the delegate population offers insights into the network's governance dynamics and the
-        distribution of trust and responsibility among participating neurons.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
-            block_hash = None if block == None else self.substrate.get_block_hash(block)
+            block_hash = None if block is None else self.substrate.get_block_hash(block)
             params = []
             if block_hash:
-                params = params + [block_hash]
+                params.extend([block_hash])
             return self.substrate.rpc_request(
                 method="delegateInfo_getDelegates",  # custom rpc method
                 params=params,
@@ -3624,7 +3780,7 @@ def get_delegated(
         involvement in the network's delegation and consensus mechanisms.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry(encoded_coldkey: List[int]):
             block_hash = None if block == None else self.substrate.get_block_hash(block)
             params = [encoded_coldkey]
@@ -3726,7 +3882,7 @@ def get_stake_info_for_coldkeys(
     def get_minimum_required_stake(
         self,
     ):
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query(
                 module="SubtensorModule", storage_function="NominatorMinRequiredStake"
@@ -3983,7 +4139,7 @@ def neuron_for_uid(
         if uid is None:
             return NeuronInfo._null_neuron()
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             block_hash = None if block == None else self.substrate.get_block_hash(block)
             params = [netuid, uid]
@@ -4114,7 +4270,7 @@ def metagraph(
         netuid: int,
         lite: bool = True,
         block: Optional[int] = None,
-    ) -> "bittensor.metagraph":
+    ) -> "bittensor.metagraph":  # type: ignore
         """
         Returns a synced metagraph for a specified subnet within the Bittensor network. The metagraph
         represents the network's structure, including neuron connections and interactions.
@@ -4293,7 +4449,7 @@ def _do_delegation(
         wait_for_inclusion: bool = True,
         wait_for_finalization: bool = False,
     ) -> bool:
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -4327,7 +4483,7 @@ def _do_undelegation(
         wait_for_inclusion: bool = True,
         wait_for_finalization: bool = False,
     ) -> bool:
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -4362,7 +4518,7 @@ def _do_nominate(
         wait_for_inclusion: bool = True,
         wait_for_finalization: bool = False,
     ) -> bool:
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             call = self.substrate.compose_call(
                 call_module="SubtensorModule",
@@ -4485,7 +4641,7 @@ def get_balance(self, address: str, block: Optional[int] = None) -> Balance:
         """
         try:
 
-            @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+            @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
             def make_substrate_call_with_retry():
                 return self.substrate.query(
                     module="System",
@@ -4497,9 +4653,9 @@ def make_substrate_call_with_retry():
                 )
 
             result = make_substrate_call_with_retry()
-        except scalecodec.exceptions.RemainingScaleBytesNotEmptyException:
-            bittensor.logging.error(
-                "Your wallet it legacy formatted, you need to run btcli stake --ammount 0 to reformat it."
+        except RemainingScaleBytesNotEmptyException:
+            _logger.error(
+                "Received a corrupted message. This likely points to an error with the network or subnet."
             )
             return Balance(1000)
         return Balance(result.value["data"]["free"])
@@ -4516,7 +4672,7 @@ def get_current_block(self) -> int:
         operations on the blockchain. It serves as a reference point for network activities and data synchronization.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.get_block_number(None)
 
@@ -4537,7 +4693,7 @@ def get_balances(self, block: Optional[int] = None) -> Dict[str, Balance]:
         including the distribution of financial resources and the financial status of network participants.
         """
 
-        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logger)
+        @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger)
         def make_substrate_call_with_retry():
             return self.substrate.query_map(
                 module="System",
@@ -4597,3 +4753,20 @@ def get_block_hash(self, block_id: int) -> str:
         maintaining the trustworthiness of the blockchain.
         """
         return self.substrate.get_block_hash(block_id=block_id)
+
+    def get_error_info_by_index(self, error_index: int) -> Tuple[str, str]:
+        """Returns the error name and description from the Subtensor error list."""
+
+        unknown_error = ("Unknown Error", "")
+
+        if not self._subtensor_errors:
+            self._subtensor_errors = get_subtensor_errors(self.substrate)
+
+        name, description = self._subtensor_errors.get(str(error_index), unknown_error)
+
+        if name == unknown_error[0]:
+            _logger.warning(
+                f"Subtensor returned an error with an unknown index: {error_index}"
+            )
+
+        return name, description
diff --git a/bittensor/synapse.py b/bittensor/synapse.py
index d4f210670e..805e8bc617 100644
--- a/bittensor/synapse.py
+++ b/bittensor/synapse.py
@@ -21,8 +21,13 @@
 import json
 import sys
 
-import pydantic
-from pydantic.schema import schema
+from pydantic import (
+    BaseModel,
+    ConfigDict,
+    Field,
+    field_validator,
+    model_validator,
+)
 import bittensor
 from typing import Optional, List, Any, Dict
 
@@ -92,7 +97,7 @@ def cast_float(raw: str) -> float:
     return float(raw) if raw != None else raw  # type: ignore
 
 
-class TerminalInfo(pydantic.BaseModel):
+class TerminalInfo(BaseModel):
     """
     TerminalInfo encapsulates detailed information about a network synapse (node) involved in a communication process.
 
@@ -144,112 +149,115 @@ class TerminalInfo(pydantic.BaseModel):
     TerminalInfo plays a pivotal role in providing transparency and control over network operations, making it an indispensable tool for developers and users interacting with the Bittensor ecosystem.
     """
 
-    class Config:
-        validate_assignment = True
+    model_config = ConfigDict(validate_assignment=True)
 
     # The HTTP status code from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
-    status_code: Optional[int] = pydantic.Field(
+    status_code: Optional[int] = Field(
         title="status_code",
         description="The HTTP status code from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status",
-        examples=200,
+        examples=[200],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
-    _extract_status_code = pydantic.validator(
-        "status_code", pre=True, allow_reuse=True
-    )(cast_int)
 
     # The HTTP status code from: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
-    status_message: Optional[str] = pydantic.Field(
+    status_message: Optional[str] = Field(
         title="status_message",
         description="The status_message associated with the status_code",
-        examples="Success",
+        examples=["Success"],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
 
     # Process time on this terminal side of call
-    process_time: Optional[float] = pydantic.Field(
+    process_time: Optional[float] = Field(
         title="process_time",
         description="Process time on this terminal side of call",
-        examples=0.1,
+        examples=[0.1],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
-    _extract_process_time = pydantic.validator(
-        "process_time", pre=True, allow_reuse=True
-    )(cast_float)
 
     # The terminal ip.
-    ip: Optional[str] = pydantic.Field(
+    ip: Optional[str] = Field(
         title="ip",
-        description="The ip of the axon recieving the request.",
-        examples="198.123.23.1",
+        description="The ip of the axon receiving the request.",
+        examples=["198.123.23.1"],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
 
     # The host port of the terminal.
-    port: Optional[int] = pydantic.Field(
+    port: Optional[int] = Field(
         title="port",
         description="The port of the terminal.",
-        examples="9282",
+        examples=["9282"],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
-    _extract_port = pydantic.validator("port", pre=True, allow_reuse=True)(cast_int)
 
     # The bittensor version on the terminal as an int.
-    version: Optional[int] = pydantic.Field(
+    version: Optional[int] = Field(
         title="version",
         description="The bittensor version on the axon as str(int)",
-        examples=111,
+        examples=[111],
         default=None,
-        allow_mutation=True,
-    )
-    _extract_version = pydantic.validator("version", pre=True, allow_reuse=True)(
-        cast_int
+        frozen=False,
     )
 
-    # A unique monotonically increasing integer nonce associate with the terminal
-    nonce: Optional[int] = pydantic.Field(
+    # A Unix timestamp to associate with the terminal
+    nonce: Optional[int] = Field(
         title="nonce",
-        description="A unique monotonically increasing integer nonce associate with the terminal generated from time.monotonic_ns()",
-        examples=111111,
+        description="A Unix timestamp that prevents replay attacks",
+        examples=[111111],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
-    _extract_nonce = pydantic.validator("nonce", pre=True, allow_reuse=True)(cast_int)
 
     # A unique identifier associated with the terminal, set on the axon side.
-    uuid: Optional[str] = pydantic.Field(
+    uuid: Optional[str] = Field(
         title="uuid",
         description="A unique identifier associated with the terminal",
-        examples="5ecbd69c-1cec-11ee-b0dc-e29ce36fec1a",
+        examples=["5ecbd69c-1cec-11ee-b0dc-e29ce36fec1a"],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
 
     # The bittensor version on the terminal as an int.
-    hotkey: Optional[str] = pydantic.Field(
+    hotkey: Optional[str] = Field(
         title="hotkey",
         description="The ss58 encoded hotkey string of the terminal wallet.",
-        examples="5EnjDGNqqWnuL2HCAdxeEtN2oqtXZw6BMBe936Kfy2PFz1J1",
+        examples=["5EnjDGNqqWnuL2HCAdxeEtN2oqtXZw6BMBe936Kfy2PFz1J1"],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
 
     # A signature verifying the tuple (axon_nonce, axon_hotkey, dendrite_hotkey, axon_uuid)
-    signature: Optional[str] = pydantic.Field(
+    signature: Optional[str] = Field(
         title="signature",
         description="A signature verifying the tuple (nonce, axon_hotkey, dendrite_hotkey, uuid)",
-        examples="0x0813029319030129u4120u10841824y0182u091u230912u",
+        examples=["0x0813029319030129u4120u10841824y0182u091u230912u"],
         default=None,
-        allow_mutation=True,
+        frozen=False,
     )
 
+    # Extract the process time on this terminal side of call as a float
+    _extract_process_time = field_validator("process_time", mode="before")(cast_float)
+
+    # Extract the host port of the terminal as an int
+    _extract_port = field_validator("port", mode="before")(cast_int)
+
+    # Extract the bittensor version on the terminal as an int.
+    _extract_version = field_validator("version", mode="before")(cast_int)
+
+    # Extract the Unix timestamp associated with the terminal as an int
+    _extract_nonce = field_validator("nonce", mode="before")(cast_int)
 
-class Synapse(pydantic.BaseModel):
+    # Extract the HTTP status code as an int
+    _extract_status_code = field_validator("status_code", mode="before")(cast_int)
+
+
+class Synapse(BaseModel):
     """
     Represents a Synapse in the Bittensor network, serving as a communication schema between neurons (nodes).
 
@@ -315,7 +323,7 @@ class Synapse(pydantic.BaseModel):
         synapse.dummy_input = 1 # This will raise an error because dummy_input is not defined in the Synapse class
 
         # Get a dictionary of headers and body from the synapse instance
-        synapse_dict = synapse.json()
+        synapse_dict = synapse.model_dump_json()
 
         # Get a dictionary of headers from the synapse instance
         headers = synapse.to_headers()
@@ -357,8 +365,7 @@ class Synapse(pydantic.BaseModel):
     standardized communication in a decentralized environment.
     """
 
-    class Config:
-        validate_assignment = True
+    model_config = ConfigDict(validate_assignment=True)
 
     def deserialize(self) -> "Synapse":
         """
@@ -387,7 +394,7 @@ def deserialize(self) -> "CustomSynapse":
                     return self
 
             serialized_data = '{"additional_data": "SGVsbG8gV29ybGQ="}'  # Base64 for 'Hello World'
-            custom_synapse = CustomSynapse.parse_raw(serialized_data)
+            custom_synapse = CustomSynapse.model_validate_json(serialized_data)
             deserialized_synapse = custom_synapse.deserialize()
 
             # deserialized_synapse.additional_data would now be 'Hello World'
@@ -397,98 +404,95 @@ def deserialize(self) -> "CustomSynapse":
         """
         return self
 
-    @pydantic.root_validator(pre=True)
+    @model_validator(mode="before")
     def set_name_type(cls, values) -> dict:
         values["name"] = cls.__name__  # type: ignore
         return values
 
     # Defines the http route name which is set on axon.attach( callable( request: RequestName ))
-    name: Optional[str] = pydantic.Field(
+    name: Optional[str] = Field(
         title="name",
         description="Defines the http route name which is set on axon.attach( callable( request: RequestName ))",
-        examples="Forward",
-        allow_mutation=True,
+        examples=["Forward"],
+        frozen=False,
         default=None,
         repr=False,
     )
 
     # The call timeout, set by the dendrite terminal.
-    timeout: Optional[float] = pydantic.Field(
+    timeout: Optional[float] = Field(
         title="timeout",
         description="Defines the total query length.",
-        examples=12.0,
+        examples=[12.0],
         default=12.0,
-        allow_mutation=True,
+        frozen=False,
         repr=False,
     )
-    _extract_timeout = pydantic.validator("timeout", pre=True, allow_reuse=True)(
-        cast_float
-    )
 
     # The call timeout, set by the dendrite terminal.
-    total_size: Optional[int] = pydantic.Field(
+    total_size: Optional[int] = Field(
         title="total_size",
         description="Total size of request body in bytes.",
-        examples=1000,
+        examples=[1000],
         default=0,
-        allow_mutation=True,
+        frozen=False,
         repr=False,
     )
-    _extract_total_size = pydantic.validator("total_size", pre=True, allow_reuse=True)(
-        cast_int
-    )
 
     # The call timeout, set by the dendrite terminal.
-    header_size: Optional[int] = pydantic.Field(
+    header_size: Optional[int] = Field(
         title="header_size",
         description="Size of request header in bytes.",
-        examples=1000,
+        examples=[1000],
         default=0,
-        allow_mutation=True,
+        frozen=False,
         repr=False,
     )
-    _extract_header_size = pydantic.validator(
-        "header_size", pre=True, allow_reuse=True
-    )(cast_int)
 
     # The dendrite Terminal Information.
-    dendrite: Optional[TerminalInfo] = pydantic.Field(
+    dendrite: Optional[TerminalInfo] = Field(
         title="dendrite",
         description="Dendrite Terminal Information",
-        examples="bittensor.TerminalInfo",
+        examples=["bittensor.TerminalInfo"],
         default=TerminalInfo(),
-        allow_mutation=True,
+        frozen=False,
         repr=False,
     )
 
     # A axon terminal information
-    axon: Optional[TerminalInfo] = pydantic.Field(
+    axon: Optional[TerminalInfo] = Field(
         title="axon",
         description="Axon Terminal Information",
-        examples="bittensor.TerminalInfo",
+        examples=["bittensor.TerminalInfo"],
         default=TerminalInfo(),
-        allow_mutation=True,
+        frozen=False,
         repr=False,
     )
 
-    computed_body_hash: Optional[str] = pydantic.Field(
+    computed_body_hash: Optional[str] = Field(
         title="computed_body_hash",
         description="The computed body hash of the request.",
-        examples="0x0813029319030129u4120u10841824y0182u091u230912u",
+        examples=["0x0813029319030129u4120u10841824y0182u091u230912u"],
         default="",
-        allow_mutation=False,
+        frozen=True,
         repr=False,
     )
 
-    required_hash_fields: Optional[List[str]] = pydantic.Field(
+    required_hash_fields: Optional[List[str]] = Field(
         title="required_hash_fields",
         description="The list of required fields to compute the body hash.",
         examples=["roles", "messages"],
         default=[],
-        allow_mutation=False,
+        frozen=True,
         repr=False,
     )
 
+    _extract_total_size = field_validator("total_size", mode="before")(cast_int)
+
+    _extract_header_size = field_validator("header_size", mode="before")(cast_int)
+
+    _extract_timeout = field_validator("timeout", mode="before")(cast_float)
+
     def __setattr__(self, name: str, value: Any):
         """
         Override the :func:`__setattr__` method to make the ``required_hash_fields`` property read-only.
@@ -580,6 +584,13 @@ def failed_verification(self) -> bool:
         """
         return self.dendrite is not None and self.dendrite.status_code == 401
 
+    def get_required_fields(self):
+        """
+        Get the required fields from the model's JSON schema.
+        """
+        schema = self.__class__.model_json_schema()
+        return schema.get("required", [])
+
     def to_headers(self) -> dict:
         """
         Converts the state of a Synapse instance into a dictionary of HTTP headers.
@@ -612,7 +623,7 @@ def to_headers(self) -> dict:
             headers.update(
                 {
                     f"bt_header_axon_{k}": str(v)
-                    for k, v in self.axon.dict().items()
+                    for k, v in self.axon.model_dump().items()
                     if v is not None
                 }
             )
@@ -620,20 +631,18 @@ def to_headers(self) -> dict:
             headers.update(
                 {
                     f"bt_header_dendrite_{k}": str(v)
-                    for k, v in self.dendrite.dict().items()
+                    for k, v in self.dendrite.model_dump().items()
                     if v is not None
                 }
             )
 
         # Getting the fields of the instance
-        instance_fields = self.dict()
+        instance_fields = self.model_dump()
 
         # Iterating over the fields of the instance
         for field, value in instance_fields.items():
             # If the object is not optional, serializing it, encoding it, and adding it to the headers
-            required = schema([self.__class__])["definitions"][self.name].get(
-                "required"
-            )
+            required = self.get_required_fields()
 
             # Skipping the field if it's already in the headers or its value is None
             if field in headers or value is None:
@@ -687,7 +696,7 @@ def body_hash(self) -> str:
         hashes = []
 
         # Getting the fields of the instance
-        instance_fields = self.dict()
+        instance_fields = self.model_dump()
 
         for field, value in instance_fields.items():
             # If the field is required in the subclass schema, hash and add it.
diff --git a/bittensor/tensor.py b/bittensor/tensor.py
index 46b4e30c83..ab46560d99 100644
--- a/bittensor/tensor.py
+++ b/bittensor/tensor.py
@@ -16,56 +16,96 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-import numpy
-import torch
+import numpy as np
 import base64
 import msgpack
-import pydantic
 import msgpack_numpy
 from typing import Optional, Union, List
+from bittensor.utils.registration import torch, use_torch
+from pydantic import ConfigDict, BaseModel, Field, field_validator
+
+
+class DTypes(dict):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.torch: bool = False
+        self.update(
+            {
+                "float16": np.float16,
+                "float32": np.float32,
+                "float64": np.float64,
+                "uint8": np.uint8,
+                "int16": np.int16,
+                "int8": np.int8,
+                "int32": np.int32,
+                "int64": np.int64,
+                "bool": bool,
+            }
+        )
+
+    def __getitem__(self, key):
+        self._add_torch()
+        return super().__getitem__(key)
+
+    def __contains__(self, key):
+        self._add_torch()
+        return super().__contains__(key)
+
+    def _add_torch(self):
+        if self.torch is False:
+            torch_dtypes = {
+                "torch.float16": torch.float16,
+                "torch.float32": torch.float32,
+                "torch.float64": torch.float64,
+                "torch.uint8": torch.uint8,
+                "torch.int16": torch.int16,
+                "torch.int8": torch.int8,
+                "torch.int32": torch.int32,
+                "torch.int64": torch.int64,
+                "torch.bool": torch.bool,
+            }
+            self.update(torch_dtypes)
+            self.torch = True
 
-TORCH_DTYPES = {
-    "torch.float16": torch.float16,
-    "torch.float32": torch.float32,
-    "torch.float64": torch.float64,
-    "torch.uint8": torch.uint8,
-    "torch.int16": torch.int16,
-    "torch.int8": torch.int8,
-    "torch.int32": torch.int32,
-    "torch.int64": torch.int64,
-    "torch.bool": torch.bool,
-}
 
+dtypes = DTypes()
 
-def cast_dtype(raw: Union[None, torch.dtype, str]) -> str:
+
+def cast_dtype(raw: Union[None, np.dtype, "torch.dtype", str]) -> Optional[str]:
     """
-    Casts the raw value to a string representing the `torch data type <https://pytorch.org/docs/stable/tensor_attributes.html>`_.
+    Casts the raw value to a string representing the
+    `numpy data type <https://numpy.org/doc/stable/user/basics.types.html>`_, or the
+    `torch data type <https://pytorch.org/docs/stable/tensor_attributes.html>`_ if using torch.
 
     Args:
-        raw (Union[None, torch.dtype, str]): The raw value to cast.
+        raw (Union[None, numpy.dtype, torch.dtype, str]): The raw value to cast.
 
     Returns:
-        str: The string representing the torch data type.
+        str: The string representing the numpy/torch data type.
 
     Raises:
         Exception: If the raw value is of an invalid type.
     """
     if not raw:
         return None
-    if isinstance(raw, torch.dtype):
-        return TORCH_DTYPES[raw]
+    if use_torch() and isinstance(raw, torch.dtype):
+        return dtypes[raw]
+    elif isinstance(raw, np.dtype):
+        return dtypes[raw]
     elif isinstance(raw, str):
-        assert (
-            raw in TORCH_DTYPES
-        ), f"{str} not a valid torch type in dict {TORCH_DTYPES}"
-        return raw
+        if use_torch():
+            assert raw in dtypes, f"{raw} not a valid torch type in dict {dtypes}"
+            return raw
+        else:
+            assert raw in dtypes, f"{raw} not a valid numpy type in dict {dtypes}"
+            return raw
     else:
         raise Exception(
-            f"{raw} of type {type(raw)} does not have a valid type in Union[None, torch.dtype, str]"
+            f"{raw} of type {type(raw)} does not have a valid type in Union[None, numpy.dtype, torch.dtype, str]"
         )
 
 
-def cast_shape(raw: Union[None, List[int], str]) -> str:
+def cast_shape(raw: Union[None, List[int], str]) -> Optional[Union[str, list]]:
     """
     Casts the raw value to a string representing the tensor shape.
 
@@ -81,9 +121,7 @@ def cast_shape(raw: Union[None, List[int], str]) -> str:
     if not raw:
         return None
     elif isinstance(raw, list):
-        if len(raw) == 0:
-            return raw
-        elif isinstance(raw[0], int):
+        if len(raw) == 0 or isinstance(raw[0], int):
             return raw
         else:
             raise Exception(f"{raw} list elements are not of type int")
@@ -97,15 +135,13 @@ def cast_shape(raw: Union[None, List[int], str]) -> str:
 
 
 class tensor:
-    def __new__(cls, tensor: Union[list, numpy.ndarray, torch.Tensor]):
-        if isinstance(tensor, list):
-            tensor = torch.tensor(tensor)
-        elif isinstance(tensor, numpy.ndarray):
-            tensor = torch.tensor(tensor)
-        return Tensor.serialize(tensor=tensor)
+    def __new__(cls, tensor: Union[list, np.ndarray, "torch.Tensor"]):
+        if isinstance(tensor, list) or isinstance(tensor, np.ndarray):
+            tensor = torch.tensor(tensor) if use_torch() else np.array(tensor)
+        return Tensor.serialize(tensor_=tensor)
 
 
-class Tensor(pydantic.BaseModel):
+class Tensor(BaseModel):
     """
     Represents a Tensor object.
 
@@ -115,24 +151,25 @@ class Tensor(pydantic.BaseModel):
         shape (List[int]): Tensor shape.
     """
 
-    class Config:
-        validate_assignment = True
+    model_config = ConfigDict(validate_assignment=True)
 
-    def tensor(self) -> torch.Tensor:
+    def tensor(self) -> Union[np.ndarray, "torch.Tensor"]:
         return self.deserialize()
 
     def tolist(self) -> List[object]:
         return self.deserialize().tolist()
 
     def numpy(self) -> "numpy.ndarray":
-        return self.deserialize().detach().numpy()
+        return (
+            self.deserialize().detach().numpy() if use_torch() else self.deserialize()
+        )
 
-    def deserialize(self) -> "torch.Tensor":
+    def deserialize(self) -> Union["np.ndarray", "torch.Tensor"]:
         """
         Deserializes the Tensor object.
 
         Returns:
-            torch.Tensor: The deserialized tensor object.
+            np.array or torch.Tensor: The deserialized tensor object.
 
         Raises:
             Exception: If the deserialization process encounters an error.
@@ -142,19 +179,25 @@ def deserialize(self) -> "torch.Tensor":
         numpy_object = msgpack.unpackb(
             buffer_bytes, object_hook=msgpack_numpy.decode
         ).copy()
-        torch_object = torch.as_tensor(numpy_object)
-        # Reshape does not work for (0) or [0]
-        if not (len(shape) == 1 and shape[0] == 0):
-            torch_object = torch_object.reshape(shape)
-        return torch_object.type(TORCH_DTYPES[self.dtype])
+        if use_torch():
+            torch_object = torch.as_tensor(numpy_object)
+            # Reshape does not work for (0) or [0]
+            if not (len(shape) == 1 and shape[0] == 0):
+                torch_object = torch_object.reshape(shape)
+            return torch_object.type(dtypes[self.dtype])
+        else:
+            # Reshape does not work for (0) or [0]
+            if not (len(shape) == 1 and shape[0] == 0):
+                numpy_object = numpy_object.reshape(shape)
+            return numpy_object.astype(dtypes[self.dtype])
 
     @staticmethod
-    def serialize(tensor: "torch.Tensor") -> "Tensor":
+    def serialize(tensor_: Union["np.ndarray", "torch.Tensor"]) -> "Tensor":
         """
         Serializes the given tensor.
 
         Args:
-            tensor (torch.Tensor): The tensor to serialize.
+            tensor_ (np.array or torch.Tensor): The tensor to serialize.
 
         Returns:
             Tensor: The serialized tensor.
@@ -162,38 +205,46 @@ def serialize(tensor: "torch.Tensor") -> "Tensor":
         Raises:
             Exception: If the serialization process encounters an error.
         """
-        dtype = str(tensor.dtype)
-        shape = list(tensor.shape)
+        dtype = str(tensor_.dtype)
+        shape = list(tensor_.shape)
         if len(shape) == 0:
             shape = [0]
-        torch_numpy = tensor.cpu().detach().numpy().copy()
+        tensor__ = tensor_.cpu().detach().numpy().copy() if use_torch() else tensor_
         data_buffer = base64.b64encode(
-            msgpack.packb(torch_numpy, default=msgpack_numpy.encode)
+            msgpack.packb(tensor__, default=msgpack_numpy.encode)
         ).decode("utf-8")
         return Tensor(buffer=data_buffer, shape=shape, dtype=dtype)
 
-    buffer: Optional[str] = pydantic.Field(
+    # Represents the tensor buffer data.
+    buffer: Optional[str] = Field(
+        default=None,
         title="buffer",
         description="Tensor buffer data. This field stores the serialized representation of the tensor data.",
-        examples="0x321e13edqwds231231231232131",
-        allow_mutation=False,
+        examples=["0x321e13edqwds231231231232131"],
+        frozen=True,
         repr=False,
-    )  # Represents the tensor buffer data.
+    )
 
-    dtype: str = pydantic.Field(
+    # Represents the data type of the tensor.
+    dtype: str = Field(
         title="dtype",
-        description="Tensor data type. This field specifies the data type of the tensor, such as torch.float32 or torch.int64.",
-        examples="torch.float32",
-        allow_mutation=False,
+        description="Tensor data type. This field specifies the data type of the tensor, such as numpy.float32 or torch.int64.",
+        examples=["np.float32"],
+        frozen=True,
         repr=True,
-    )  # Represents the data type of the tensor.
-    _extract_dtype = pydantic.validator("dtype", pre=True, allow_reuse=True)(cast_dtype)
+    )
 
-    shape: List[int] = pydantic.Field(
+    # Represents the shape of the tensor.
+    shape: List[int] = Field(
         title="shape",
         description="Tensor shape. This field defines the dimensions of the tensor as a list of integers, such as [10, 10] for a 2D tensor with shape (10, 10).",
-        examples="[10,10]",
-        allow_mutation=False,
+        examples=[10, 10],
+        frozen=True,
         repr=True,
-    )  # Represents the shape of the tensor.
-    _extract_shape = pydantic.validator("shape", pre=True, allow_reuse=True)(cast_shape)
+    )
+
+    # Extract the represented shape of the tensor.
+    _extract_shape = field_validator("shape", mode="before")(cast_shape)
+
+    # Extract the represented data type of the tensor.
+    _extract_dtype = field_validator("dtype", mode="before")(cast_dtype)
diff --git a/bittensor/threadpool.py b/bittensor/threadpool.py
index c20d64a6b3..3e49786ff6 100644
--- a/bittensor/threadpool.py
+++ b/bittensor/threadpool.py
@@ -225,14 +225,14 @@ def submit(self, fn: Callable, *args, **kwargs) -> _base.Future:
             priority = kwargs.get("priority", random.randint(0, 1000000))
             if priority == 0:
                 priority = random.randint(1, 100)
-            eplison = random.uniform(0, 0.01) * priority
+            epsilon = random.uniform(0, 0.01) * priority
             start_time = time.time()
             if "priority" in kwargs:
                 del kwargs["priority"]
 
             f = _base.Future()
             w = _WorkItem(f, fn, start_time, args, kwargs)
-            self._work_queue.put((-float(priority + eplison), w), block=False)
+            self._work_queue.put((-float(priority + epsilon), w), block=False)
             self._adjust_thread_count()
             return f
 
diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py
index fbc3aa7d47..72d053ea7a 100644
--- a/bittensor/utils/__init__.py
+++ b/bittensor/utils/__init__.py
@@ -1,6 +1,7 @@
 # The MIT License (MIT)
 # Copyright © 2022 Opentensor Foundation
 # Copyright © 2023 Opentensor Technologies Inc
+import os
 
 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 # documentation files (the “Software”), to deal in the Software without restriction, including without limitation
@@ -16,15 +17,17 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-from typing import Callable, Union, List, Optional, Dict, Literal
+from typing import Callable, List, Dict, Literal, Tuple
 
 import bittensor
 import hashlib
 import requests
-import torch
 import scalecodec
+import numpy as np
 
 from .wallet_utils import *  # noqa F401
+from .version import version_checking, check_version, VersionCheckError
+from .registration import torch, use_torch
 
 RAOPERTAO = 1e9
 U16_MAX = 65535
@@ -37,55 +40,99 @@ def ss58_to_vec_u8(ss58_address: str) -> List[int]:
     return encoded_address
 
 
-def unbiased_topk(values, k, dim=0, sorted=True, largest=True):
-    r"""Selects topk as in torch.topk but does not bias lower indices when values are equal.
+def _unbiased_topk(
+    values: Union[np.ndarray, "torch.Tensor"],
+    k: int,
+    dim=0,
+    sorted=True,
+    largest=True,
+    axis=0,
+    return_type: str = "numpy",
+) -> Union[Tuple[np.ndarray, np.ndarray], Tuple["torch.Tensor", "torch.LongTensor"]]:
+    """Selects topk as in torch.topk but does not bias lower indices when values are equal.
     Args:
-        values: (torch.Tensor)
+        values: (np.ndarray) if using numpy, (torch.Tensor) if using torch:
             Values to index into.
         k: (int):
             Number to take.
+        dim: (int):
+            Dimension to index into (used by Torch)
+        sorted: (bool):
+            Whether to sort indices.
+        largest: (bool):
+            Whether to take the largest value.
+        axis: (int):
+            Axis along which to index into (used by Numpy)
+        return_type: (str):
+            Whether or use torch or numpy approach
 
     Return:
-        topk: (torch.Tensor):
+        topk: (np.ndarray) if using numpy, (torch.Tensor) if using torch:
             topk k values.
-        indices: (torch.LongTensor)
+        indices: (np.ndarray) if using numpy, (torch.LongTensor) if using torch:
             indices of the topk values.
     """
-    permutation = torch.randperm(values.shape[dim])
-    permuted_values = values[permutation]
-    topk, indices = torch.topk(
-        permuted_values, k, dim=dim, sorted=sorted, largest=largest
-    )
-    return topk, permutation[indices]
-
+    if return_type == "torch":
+        permutation = torch.randperm(values.shape[dim])
+        permuted_values = values[permutation]
+        topk, indices = torch.topk(
+            permuted_values, k, dim=dim, sorted=sorted, largest=largest
+        )
+        return topk, permutation[indices]
+    else:
+        if dim != 0 and axis == 0:
+            # Ensures a seamless transition for calls made to this function that specified args by keyword
+            axis = dim
+
+        permutation = np.random.permutation(values.shape[axis])
+        permuted_values = np.take(values, permutation, axis=axis)
+        indices = np.argpartition(permuted_values, -k, axis=axis)[-k:]
+        if not sorted:
+            indices = np.sort(indices, axis=axis)
+        if not largest:
+            indices = indices[::-1]
+        topk = np.take(permuted_values, indices, axis=axis)
+        return topk, permutation[indices]
+
+
+def unbiased_topk(
+    values: Union[np.ndarray, "torch.Tensor"],
+    k: int,
+    dim: int = 0,
+    sorted: bool = True,
+    largest: bool = True,
+    axis: int = 0,
+) -> Union[Tuple[np.ndarray, np.ndarray], Tuple["torch.Tensor", "torch.LongTensor"]]:
+    """Selects topk as in torch.topk but does not bias lower indices when values are equal.
+    Args:
+        values: (np.ndarray) if using numpy, (torch.Tensor) if using torch:
+            Values to index into.
+        k: (int):
+            Number to take.
+        dim: (int):
+            Dimension to index into (used by Torch)
+        sorted: (bool):
+            Whether to sort indices.
+        largest: (bool):
+            Whether to take the largest value.
+        axis: (int):
+            Axis along which to index into (used by Numpy)
 
-def version_checking(timeout: int = 15):
-    try:
-        bittensor.logging.debug(
-            f"Checking latest Bittensor version at: {bittensor.__pipaddress__}"
+    Return:
+        topk: (np.ndarray) if using numpy, (torch.Tensor) if using torch:
+            topk k values.
+        indices: (np.ndarray) if using numpy, (torch.LongTensor) if using torch:
+            indices of the topk values.
+    """
+    if use_torch():
+        return _unbiased_topk(
+            values, k, dim, sorted, largest, axis, return_type="torch"
         )
-        response = requests.get(bittensor.__pipaddress__, timeout=timeout)
-        latest_version = response.json()["info"]["version"]
-        version_split = latest_version.split(".")
-        latest_version_as_int = (
-            (100 * int(version_split[0]))
-            + (10 * int(version_split[1]))
-            + (1 * int(version_split[2]))
+    else:
+        return _unbiased_topk(
+            values, k, dim, sorted, largest, axis, return_type="numpy"
         )
 
-        if latest_version_as_int > bittensor.__version_as_int__:
-            print(
-                "\u001b[33mBittensor Version: Current {}/Latest {}\nPlease update to the latest version at your earliest convenience. "
-                "Run the following command to upgrade:\n\n\u001b[0mpython -m pip install --upgrade bittensor".format(
-                    bittensor.__version__, latest_version
-                )
-            )
-
-    except requests.exceptions.Timeout:
-        bittensor.logging.error("Version check failed due to timeout")
-    except requests.exceptions.RequestException as e:
-        bittensor.logging.error(f"Version check failed due to request failure: {e}")
-
 
 def strtobool_with_default(
     default: bool,
diff --git a/bittensor/utils/registration.py b/bittensor/utils/registration.py
index b389d8176b..6c504b05f1 100644
--- a/bittensor/utils/registration.py
+++ b/bittensor/utils/registration.py
@@ -1,18 +1,21 @@
 import binascii
+import functools
 import hashlib
 import math
 import multiprocessing
 import os
 import random
 import time
+import typing
 from dataclasses import dataclass
 from datetime import timedelta
 from queue import Empty, Full
 from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 
 import backoff
+import numpy
+
 import bittensor
-import torch
 from Crypto.Hash import keccak
 from rich import console as rich_console
 from rich import status as rich_status
@@ -21,6 +24,80 @@
 from ._register_cuda import solve_cuda
 
 
+def use_torch() -> bool:
+    """Force the use of torch over numpy for certain operations."""
+    return True if os.getenv("USE_TORCH") == "1" else False
+
+
+def legacy_torch_api_compat(func):
+    """
+    Convert function operating on numpy Input&Output to legacy torch Input&Output API if `use_torch()` is True.
+
+    Args:
+        func (function):
+            Function with numpy Input/Output to be decorated.
+    Returns:
+        decorated (function):
+            Decorated function.
+    """
+
+    @functools.wraps(func)
+    def decorated(*args, **kwargs):
+        if use_torch():
+            # if argument is a Torch tensor, convert it to numpy
+            args = [
+                arg.cpu().numpy() if isinstance(arg, torch.Tensor) else arg
+                for arg in args
+            ]
+            kwargs = {
+                key: value.cpu().numpy() if isinstance(value, torch.Tensor) else value
+                for key, value in kwargs.items()
+            }
+        ret = func(*args, **kwargs)
+        if use_torch():
+            # if return value is a numpy array, convert it to Torch tensor
+            if isinstance(ret, numpy.ndarray):
+                ret = torch.from_numpy(ret)
+        return ret
+
+    return decorated
+
+
+@functools.cache
+def _get_real_torch():
+    try:
+        import torch as _real_torch
+    except ImportError:
+        _real_torch = None
+    return _real_torch
+
+
+def log_no_torch_error():
+    bittensor.btlogging.error(
+        "This command requires torch. You can install torch for bittensor"
+        ' with `pip install bittensor[torch]` or `pip install ".[torch]"`'
+        " if installing from source, and then run the command with USE_TORCH=1 {command}"
+    )
+
+
+class LazyLoadedTorch:
+    def __bool__(self):
+        return bool(_get_real_torch())
+
+    def __getattr__(self, name):
+        if real_torch := _get_real_torch():
+            return getattr(real_torch, name)
+        else:
+            log_no_torch_error()
+            raise ImportError("torch not installed")
+
+
+if typing.TYPE_CHECKING:
+    import torch
+else:
+    torch = LazyLoadedTorch()
+
+
 class CUDAException(Exception):
     """An exception raised when an error occurs in the CUDA environment."""
 
diff --git a/bittensor/utils/subtensor.py b/bittensor/utils/subtensor.py
new file mode 100644
index 0000000000..484184e77f
--- /dev/null
+++ b/bittensor/utils/subtensor.py
@@ -0,0 +1,139 @@
+# The MIT License (MIT)
+# Copyright © 2021 Yuma Rao
+# Copyright © 2022 Opentensor Foundation
+# Copyright © 2023 Opentensor Technologies Inc
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Module providing common helper functions for working with Subtensor."""
+
+import json
+import logging
+import os
+from typing import Dict, Optional, Union, Any
+
+from substrateinterface.base import SubstrateInterface
+
+_logger = logging.getLogger("subtensor.errors_handler")
+
+_USER_HOME_DIR = os.path.expanduser("~")
+_BT_DIR = os.path.join(_USER_HOME_DIR, ".bittensor")
+_ERRORS_FILE_PATH = os.path.join(_BT_DIR, "subtensor_errors_map.json")
+_ST_BUILD_ID = "subtensor_build_id"
+
+# Create directory if it doesn't exist
+os.makedirs(_BT_DIR, exist_ok=True)
+
+
+# Pallet's typing class `PalletMetadataV14` is defined only at
+# https://github.com/polkascan/py-scale-codec/blob/master/scalecodec/type_registry/core.json#L1024
+# A class object is created dynamically at runtime.
+# Circleci linter complains about string represented classes like 'PalletMetadataV14'.
+def _get_errors_from_pallet(pallet) -> Optional[Dict[str, Dict[str, str]]]:
+    """Extracts and returns error information from the given pallet metadata.
+
+    Args:
+        pallet (PalletMetadataV14): The pallet metadata containing error definitions.
+
+    Returns:
+        dict[str, str]: A dictionary of errors indexed by their IDs.
+
+    Raises:
+        ValueError: If the pallet does not contain error definitions or the list is empty.
+    """
+    if not hasattr(pallet, "errors") or not pallet.errors:
+        _logger.warning(
+            "The pallet does not contain any error definitions or the list is empty."
+        )
+        return None
+
+    return {
+        str(error["index"]): {
+            "name": error["name"],
+            "description": " ".join(error["docs"]),
+        }
+        for error in pallet.errors
+    }
+
+
+def _save_errors_to_cache(uniq_version: str, errors: Dict[str, Dict[str, str]]):
+    """Saves error details and unique version identifier to a JSON file.
+
+    Args:
+        uniq_version (str): Unique version identifier for the Subtensor build.
+        errors (dict[str, str]): Error information to be cached.
+    """
+    data = {_ST_BUILD_ID: uniq_version, "errors": errors}
+    try:
+        with open(_ERRORS_FILE_PATH, "w") as json_file:
+            json.dump(data, json_file, indent=4)
+    except IOError as e:
+        _logger.warning(f"Error saving to file: {e}")
+
+
+def _get_errors_from_cache() -> Optional[Dict[str, Dict[str, Dict[str, str]]]]:
+    """Retrieves and returns the cached error information from a JSON file, if it exists.
+
+    Returns:
+            A dictionary containing error information.
+    """
+    if not os.path.exists(_ERRORS_FILE_PATH):
+        return None
+
+    try:
+        with open(_ERRORS_FILE_PATH, "r") as json_file:
+            data = json.load(json_file)
+    except IOError as e:
+        _logger.warning(f"Error reading from file: {e}")
+        return None
+
+    return data
+
+
+def get_subtensor_errors(
+    substrate: SubstrateInterface,
+) -> Union[Dict[str, Dict[str, str]], Dict[Any, Any]]:
+    """Fetches or retrieves cached Subtensor error definitions using metadata.
+
+    Args:
+        substrate (SubstrateInterface): Instance of SubstrateInterface to access metadata.
+
+    Returns:
+        dict[str, str]: A dictionary containing error information.
+    """
+    if not substrate.metadata:
+        substrate.get_metadata()
+
+    cached_errors_map = _get_errors_from_cache()
+    # TODO: Talk to the Nucleus team about a unique identification for each assembly (subtensor). Before that, use
+    #  the metadata value for `subtensor_build_id`
+    subtensor_build_id = substrate.metadata[0].value
+
+    if not cached_errors_map or subtensor_build_id != cached_errors_map.get(
+        _ST_BUILD_ID
+    ):
+        pallet = substrate.metadata.get_metadata_pallet("SubtensorModule")
+        subtensor_errors_map = _get_errors_from_pallet(pallet)
+
+        if not subtensor_errors_map:
+            return {}
+
+        _save_errors_to_cache(
+            uniq_version=substrate.metadata[0].value, errors=subtensor_errors_map
+        )
+        _logger.info(f"File {_ERRORS_FILE_PATH} has been updated.")
+        return subtensor_errors_map
+    else:
+        return cached_errors_map.get("errors", {})
diff --git a/bittensor/utils/version.py b/bittensor/utils/version.py
new file mode 100644
index 0000000000..2a5aa3cd57
--- /dev/null
+++ b/bittensor/utils/version.py
@@ -0,0 +1,103 @@
+from typing import Optional
+from pathlib import Path
+import time
+from packaging.version import Version
+
+import bittensor
+import requests
+
+VERSION_CHECK_THRESHOLD = 86400
+
+
+class VersionCheckError(Exception):
+    pass
+
+
+def _get_version_file_path() -> Path:
+    return Path.home() / ".bittensor" / ".last_known_version"
+
+
+def _get_version_from_file(version_file: Path) -> Optional[str]:
+    try:
+        mtime = version_file.stat().st_mtime
+        bittensor.logging.debug(f"Found version file, last modified: {mtime}")
+        diff = time.time() - mtime
+
+        if diff >= VERSION_CHECK_THRESHOLD:
+            bittensor.logging.debug("Version file expired")
+            return None
+
+        return version_file.read_text()
+    except FileNotFoundError:
+        bittensor.logging.debug("No bitensor version file found")
+        return None
+    except OSError:
+        bittensor.logging.exception("Failed to read version file")
+        return None
+
+
+def _get_version_from_pypi(timeout: int = 15) -> str:
+    bittensor.logging.debug(
+        f"Checking latest Bittensor version at: {bittensor.__pipaddress__}"
+    )
+    try:
+        response = requests.get(bittensor.__pipaddress__, timeout=timeout)
+        latest_version = response.json()["info"]["version"]
+        return latest_version
+    except requests.exceptions.RequestException:
+        bittensor.logging.exception("Failed to get latest version from pypi")
+        raise
+
+
+def get_and_save_latest_version(timeout: int = 15) -> str:
+    version_file = _get_version_file_path()
+
+    if last_known_version := _get_version_from_file(version_file):
+        return last_known_version
+
+    latest_version = _get_version_from_pypi(timeout)
+
+    try:
+        version_file.write_text(latest_version)
+    except OSError:
+        bittensor.logging.exception("Failed to save latest version to file")
+
+    return latest_version
+
+
+def check_version(timeout: int = 15):
+    """
+    Check if the current version of Bittensor is up to date with the latest version on PyPi.
+    Raises a VersionCheckError if the version check fails.
+    """
+
+    try:
+        latest_version = get_and_save_latest_version(timeout)
+
+        if Version(latest_version) > Version(bittensor.__version__):
+            print(
+                "\u001b[33mBittensor Version: Current {}/Latest {}\nPlease update to the latest version at your earliest convenience. "
+                "Run the following command to upgrade:\n\n\u001b[0mpython -m pip install --upgrade bittensor".format(
+                    bittensor.__version__, latest_version
+                )
+            )
+    except Exception as e:
+        raise VersionCheckError("Version check failed") from e
+
+
+def version_checking(timeout: int = 15):
+    """
+    Deprecated, kept for backwards compatibility. Use check_version() instead.
+    """
+
+    from warnings import warn
+
+    warn(
+        "version_checking() is deprecated, please use check_version() instead",
+        DeprecationWarning,
+    )
+
+    try:
+        check_version(timeout)
+    except VersionCheckError:
+        bittensor.logging.exception("Version check failed")
diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py
index 9190da4896..7a3d489ca6 100644
--- a/bittensor/utils/weight_utils.py
+++ b/bittensor/utils/weight_utils.py
@@ -1,4 +1,4 @@
-""" Conversion for weight between chain representation and torch tensor
+""" Conversion for weight between chain representation and np.array or torch.Tensor
 """
 
 # The MIT License (MIT)
@@ -18,9 +18,11 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-import torch
+import numpy as np
 import bittensor
-from typing import Tuple, List
+from numpy.typing import NDArray
+from typing import Tuple, List, Union
+from bittensor.utils.registration import torch, use_torch, legacy_torch_api_compat
 
 from bittensor.btlogging import logging
 
@@ -28,37 +30,38 @@
 U16_MAX = 65535
 
 
+@legacy_torch_api_compat
 def normalize_max_weight(
-    x: torch.FloatTensor, limit: float = 0.1
-) -> "torch.FloatTensor":
+    x: Union[NDArray[np.float32], "torch.FloatTensor"], limit: float = 0.1
+) -> Union[NDArray[np.float32], "torch.FloatTensor"]:
     r"""Normalizes the tensor x so that sum(x) = 1 and the max value is not greater than the limit.
     Args:
-        x (:obj:`torch.FloatTensor`):
+        x (:obj:`np.float32`):
             Tensor to be max_value normalized.
         limit: float:
             Max value after normalization.
     Returns:
-        y (:obj:`torch.FloatTensor`):
+        y (:obj:`np.float32`):
             Normalized x tensor.
     """
     epsilon = 1e-7  # For numerical stability after normalization
 
-    weights = x.clone()
-    values, _ = torch.sort(weights)
+    weights = x.copy()
+    values = np.sort(weights)
 
-    if x.sum() == 0 or len(x) * limit <= 1:
-        return torch.ones_like(x) / x.size(0)
+    if x.sum() == 0 or x.shape[0] * limit <= 1:
+        return np.ones_like(x) / x.shape[0]
     else:
         estimation = values / values.sum()
 
         if estimation.max() <= limit:
             return weights / weights.sum()
 
-        # Find the cumlative sum and sorted tensor
-        cumsum = torch.cumsum(estimation, 0)
+        # Find the cumulative sum and sorted tensor
+        cumsum = np.cumsum(estimation, 0)
 
         # Determine the index of cutoff
-        estimation_sum = torch.tensor(
+        estimation_sum = np.array(
             [(len(values) - i - 1) * estimation[i] for i in range(len(values))]
         )
         n_values = (estimation / (estimation_sum + cumsum + epsilon) < limit).sum()
@@ -79,8 +82,8 @@ def normalize_max_weight(
 
 def convert_weight_uids_and_vals_to_tensor(
     n: int, uids: List[int], weights: List[int]
-) -> "torch.FloatTensor":
-    r"""Converts weights and uids from chain representation into a torch tensor (inverse operation from convert_weights_and_uids_for_emit)
+) -> Union[NDArray[np.float32], "torch.FloatTensor"]:
+    r"""Converts weights and uids from chain representation into a np.array (inverse operation from convert_weights_and_uids_for_emit)
     Args:
         n: int:
             number of neurons on network.
@@ -89,10 +92,14 @@ def convert_weight_uids_and_vals_to_tensor(
         weights (:obj:`List[int],`):
             Tensor of weights.
     Returns:
-        row_weights ( torch.FloatTensor ):
+        row_weights ( np.float32 or torch.FloatTensor ):
             Converted row weights.
     """
-    row_weights = torch.zeros([n], dtype=torch.float32)
+    row_weights = (
+        torch.zeros([n], dtype=torch.float32)
+        if use_torch()
+        else np.zeros([n], dtype=np.float32)
+    )
     for uid_j, wij in list(zip(uids, weights)):
         row_weights[uid_j] = float(
             wij
@@ -105,8 +112,8 @@ def convert_weight_uids_and_vals_to_tensor(
 
 def convert_root_weight_uids_and_vals_to_tensor(
     n: int, uids: List[int], weights: List[int], subnets: List[int]
-) -> "torch.FloatTensor":
-    r"""Converts root weights and uids from chain representation into a torch tensor (inverse operation from convert_weights_and_uids_for_emit)
+) -> Union[NDArray[np.float32], "torch.FloatTensor"]:
+    r"""Converts root weights and uids from chain representation into a np.array or torch FloatTensor (inverse operation from convert_weights_and_uids_for_emit)
     Args:
         n: int:
             number of neurons on network.
@@ -117,11 +124,15 @@ def convert_root_weight_uids_and_vals_to_tensor(
         subnets (:obj:`List[int],`):
             list of subnets on the network
     Returns:
-        row_weights ( torch.FloatTensor ):
+        row_weights ( np.float32 ):
             Converted row weights.
     """
 
-    row_weights = torch.zeros([n], dtype=torch.float32)
+    row_weights = (
+        torch.zeros([n], dtype=torch.float32)
+        if use_torch()
+        else np.zeros([n], dtype=np.float32)
+    )
     for uid_j, wij in list(zip(uids, weights)):
         if uid_j in subnets:
             index_s = subnets.index(uid_j)
@@ -141,8 +152,8 @@ def convert_root_weight_uids_and_vals_to_tensor(
 
 def convert_bond_uids_and_vals_to_tensor(
     n: int, uids: List[int], bonds: List[int]
-) -> "torch.LongTensor":
-    r"""Converts bond and uids from chain representation into a torch tensor.
+) -> Union[NDArray[np.int64], "torch.LongTensor"]:
+    r"""Converts bond and uids from chain representation into a np.array.
     Args:
         n: int:
             number of neurons on network.
@@ -151,23 +162,28 @@ def convert_bond_uids_and_vals_to_tensor(
         bonds (:obj:`List[int],`):
             Tensor of bonds.
     Returns:
-        row_bonds ( torch.FloatTensor ):
+        row_bonds ( np.float32 ):
             Converted row bonds.
     """
-    row_bonds = torch.zeros([n], dtype=torch.int64)
+    row_bonds = (
+        torch.zeros([n], dtype=torch.int64)
+        if use_torch()
+        else np.zeros([n], dtype=np.int64)
+    )
     for uid_j, bij in list(zip(uids, bonds)):
         row_bonds[uid_j] = int(bij)
     return row_bonds
 
 
 def convert_weights_and_uids_for_emit(
-    uids: torch.LongTensor, weights: torch.FloatTensor
+    uids: Union[NDArray[np.int64], "torch.LongTensor"],
+    weights: Union[NDArray[np.float32], "torch.FloatTensor"],
 ) -> Tuple[List[int], List[int]]:
     r"""Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT.
     Args:
-        uids (:obj:`torch.LongTensor,`):
+        uids (:obj:`np.int64,`):
             Tensor of uids as destinations for passed weights.
-        weights (:obj:`torch.FloatTensor,`):
+        weights (:obj:`np.float32,`):
             Tensor of weights.
     Returns:
         weight_uids (List[int]):
@@ -214,13 +230,16 @@ def convert_weights_and_uids_for_emit(
 
 
 def process_weights_for_netuid(
-    uids,
-    weights: torch.Tensor,
+    uids: Union[NDArray[np.int64], "torch.Tensor"],
+    weights: Union[NDArray[np.float32], "torch.Tensor"],
     netuid: int,
     subtensor: "bittensor.subtensor",
     metagraph: "bittensor.metagraph" = None,
     exclude_quantile: int = 0,
-) -> torch.FloatTensor:
+) -> Union[
+    Tuple["torch.Tensor", "torch.FloatTensor"],
+    Tuple[NDArray[np.int64], NDArray[np.float32]],
+]:
     bittensor.logging.debug("process_weights_for_netuid()")
     bittensor.logging.debug("weights", weights)
     bittensor.logging.debug("netuid", netuid)
@@ -228,12 +247,16 @@ def process_weights_for_netuid(
     bittensor.logging.debug("metagraph", metagraph)
 
     # Get latest metagraph from chain if metagraph is None.
-    if metagraph == None:
+    if metagraph is None:
         metagraph = subtensor.metagraph(netuid)
 
     # Cast weights to floats.
-    if not isinstance(weights, torch.FloatTensor):
-        weights = weights.type(torch.float32)
+    if not use_torch():
+        if not isinstance(weights, torch.FloatTensor):
+            weights = weights.type(torch.float32)
+    else:
+        if not isinstance(weights, np.float32):
+            weights = weights.astype(np.float32)
 
     # Network configuration parameters from an subtensor.
     # These parameters determine the range of acceptable weights for each neuron.
@@ -245,29 +268,54 @@ def process_weights_for_netuid(
     bittensor.logging.debug("max_weight_limit", max_weight_limit)
 
     # Find all non zero weights.
-    non_zero_weight_idx = torch.argwhere(weights > 0).squeeze(dim=1)
+    non_zero_weight_idx = (
+        torch.argwhere(weights > 0).squeeze(dim=1)
+        if use_torch()
+        else np.argwhere(weights > 0).squeeze(axis=1)
+    )
     non_zero_weight_uids = uids[non_zero_weight_idx]
     non_zero_weights = weights[non_zero_weight_idx]
-    if non_zero_weights.numel() == 0 or metagraph.n < min_allowed_weights:
+    nzw_size = non_zero_weights.numel() if use_torch() else non_zero_weights.size
+    if nzw_size == 0 or metagraph.n < min_allowed_weights:
         bittensor.logging.warning("No non-zero weights returning all ones.")
-        final_weights = torch.ones((metagraph.n)).to(metagraph.n) / metagraph.n
+        final_weights = (
+            torch.ones((metagraph.n)).to(metagraph.n) / metagraph.n
+            if use_torch()
+            else np.ones((metagraph.n), dtype=np.int64) / metagraph.n
+        )
         bittensor.logging.debug("final_weights", final_weights)
-        return torch.tensor(list(range(len(final_weights)))), final_weights
+        final_weights_count = (
+            torch.tensor(list(range(len(final_weights))))
+            if use_torch()
+            else np.arange(len(final_weights))
+        )
+        return (
+            (final_weights_count, final_weights)
+            if use_torch()
+            else (final_weights_count, final_weights)
+        )
 
-    elif non_zero_weights.numel() < min_allowed_weights:
+    elif nzw_size < min_allowed_weights:
         bittensor.logging.warning(
             "No non-zero weights less then min allowed weight, returning all ones."
         )
-        # ( const ): Should this be torch.zeros( ( metagraph.n ) ) to reset everyone to build up weight?
+        # ( const ): Should this be np.zeros( ( metagraph.n ) ) to reset everyone to build up weight?
         weights = (
             torch.ones((metagraph.n)).to(metagraph.n) * 1e-5
+            if use_torch()
+            else np.ones((metagraph.n), dtype=np.int64) * 1e-5
         )  # creating minimum even non-zero weights
         weights[non_zero_weight_idx] += non_zero_weights
         bittensor.logging.debug("final_weights", weights)
         normalized_weights = bittensor.utils.weight_utils.normalize_max_weight(
             x=weights, limit=max_weight_limit
         )
-        return torch.tensor(list(range(len(normalized_weights)))), normalized_weights
+        nw_arange = (
+            torch.tensor(list(range(len(normalized_weights))))
+            if use_torch()
+            else np.arange(len(normalized_weights))
+        )
+        return nw_arange, normalized_weights
 
     bittensor.logging.debug("non_zero_weights", non_zero_weights)
 
@@ -276,7 +324,11 @@ def process_weights_for_netuid(
         non_zero_weights
     )
     exclude_quantile = min([quantile, max_exclude])
-    lowest_quantile = non_zero_weights.quantile(exclude_quantile)
+    lowest_quantile = (
+        non_zero_weights.quantile(exclude_quantile)
+        if use_torch()
+        else np.quantile(non_zero_weights, exclude_quantile)
+    )
     bittensor.logging.debug("max_exclude", max_exclude)
     bittensor.logging.debug("exclude_quantile", exclude_quantile)
     bittensor.logging.debug("lowest_quantile", lowest_quantile)
diff --git a/bittensor/wallet.py b/bittensor/wallet.py
index b123b95821..6ac808b12a 100644
--- a/bittensor/wallet.py
+++ b/bittensor/wallet.py
@@ -740,6 +740,8 @@ def regenerate_coldkey(
         if mnemonic is not None:
             if isinstance(mnemonic, str):
                 mnemonic = mnemonic.split()
+            elif isinstance(mnemonic, list) and len(mnemonic) == 1:
+                mnemonic = mnemonic[0].split()
             if len(mnemonic) not in [12, 15, 18, 21, 24]:
                 raise ValueError(
                     "Mnemonic has invalid size. This should be 12,15,18,21 or 24 words"
diff --git a/contrib/RELEASE_GUIDELINES.md b/contrib/RELEASE_GUIDELINES.md
index 906a5f463f..d6bda7c860 100644
--- a/contrib/RELEASE_GUIDELINES.md
+++ b/contrib/RELEASE_GUIDELINES.md
@@ -1,8 +1,8 @@
 # Release Guidelines
 
 The release manager in charge can release a Bittensor version using two scripts:
-  - [./scripts/release/versioning.sh](./scripts/release/versioning.sh)
-  - [./scripts/release/release.sh](./scripts/release/release.sh)
+  - [../scripts/release/versioning.sh](../scripts/release/versioning.sh)
+  - [../scripts/release/release.sh](../scripts/release/release.sh)
 
 The release manager will need the right permissions for:
   - github.com
@@ -14,6 +14,7 @@ If you are new in this role, ask for the proper setup you need to run this proce
 ## Process of release
 
 1. Create a branch called `release/VERSION`, having VERSION with the version to release.
+1. Make sure twine is installed: `pip install twine`
 1. Within the release branch:
   1. Update the version executing:`./scripts/release/versioning.sh --update UPDATE_TYPE`
     1. **UPDATE_TYPE** could be *major*, *minor* or *patch*.
diff --git a/example.env b/example.env
new file mode 100644
index 0000000000..35d405fb58
--- /dev/null
+++ b/example.env
@@ -0,0 +1,5 @@
+# To use legacy Torch-based of bittensor, you must set USE_TORCH=1
+USE_TORCH=0
+# If set to 0 (or anything else than 1), it will use current, numpy-based, bittensor interface.
+# This is generally what you want unless you want legacy interoperability.
+# Please note that the legacy interface is deprecated, and is not tested nearly as much.
diff --git a/requirements/cubit.txt b/requirements/cubit.txt
index c4e73590eb..5af1316836 100644
--- a/requirements/cubit.txt
+++ b/requirements/cubit.txt
@@ -1,2 +1,3 @@
+torch>=1.13.1
 cubit>=1.1.0
 cubit @ git+https://github.com/opentensor/cubit.git
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 71e70ae766..45c90a5b66 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,3 +1,4 @@
+aioresponses==0.7.6
 pytest==7.2.0
 pytest-mock==3.12.0
 pytest-split==0.8.0
@@ -10,3 +11,6 @@ hypothesis==6.81.1
 flake8==7.0.0
 mypy==1.8.0
 types-retry==0.9.9.4
+freezegun==1.5.0
+torch>=1.13.1
+factory-boy==3.3.0
diff --git a/requirements/prod.txt b/requirements/prod.txt
index 7a8d3edba0..d5bbf44b87 100644
--- a/requirements/prod.txt
+++ b/requirements/prod.txt
@@ -7,6 +7,7 @@ certifi==2024.2.2
 colorama==0.4.6
 cryptography==42.0.5
 ddt==1.6.0
+eth-utils<2.3.0
 fuzzywuzzy>=0.18.0
 fastapi==0.110.1
 munch==2.5.0
@@ -14,10 +15,11 @@ netaddr
 numpy
 msgpack-numpy-opentensor==0.5.0
 nest_asyncio
+packaging
 pycryptodome>=3.18.0,<4.0.0
 pyyaml
 password_strength
-pydantic!=1.8,!=1.8.1,<2.0.0,>=1.7.4
+pydantic>=2.3, <3
 PyNaCl>=1.3.0,<=1.5.0
 pytest-asyncio
 python-Levenshtein
@@ -26,11 +28,10 @@ pytest
 retry
 requests
 rich
-scalecodec==1.2.7
+scalecodec==1.2.7  # scalecodec should not be changed unless first verifying compatibility with the subtensor's monkeypatching of scalecodec.RuntimeConfiguration.get_decoder_class
 shtab==1.6.5
 substrate-interface==1.7.5
 termcolor
-torch>=1.13.1
 tqdm
 uvicorn==0.22.0
 wheel
diff --git a/requirements/torch.txt b/requirements/torch.txt
new file mode 100644
index 0000000000..028dec0810
--- /dev/null
+++ b/requirements/torch.txt
@@ -0,0 +1,1 @@
+torch>=1.13.1
diff --git a/scripts/check_pre_submit.sh b/scripts/check_pre_submit.sh
new file mode 100755
index 0000000000..7ea7c37f41
--- /dev/null
+++ b/scripts/check_pre_submit.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# black checks formating
+echo ">>> Run the pre-submit format check with \`black .\`."
+python3 -m black --exclude '(env|venv|.eggs|.git)' .
+
+echo ">>> Run the pre-submit format check with \`mypy\`."
+
+# mypy checks python versions compatibility
+versions=("3.9" "3.10" "3.11")
+for version in "${versions[@]}"; do
+    echo "Running mypy for Python $version..."
+    mypy --ignore-missing-imports bittensor/ --python-version="$version"
+done
+
+# flake8 checks errors count in bittensor folder
+error_count=$(flake8 bittensor/ --count)
+echo ">>> Flake8 found ${error_count} errors."
diff --git a/scripts/environments/README.md b/scripts/environments/README.md
index 23c14e1d5a..0caa0d2ae4 100644
--- a/scripts/environments/README.md
+++ b/scripts/environments/README.md
@@ -18,4 +18,4 @@ There are quite a few Python libraries that are not yet compatible with Apple M
     ```bash
     conda activate bittensor        # activate the env 
     pip install --no-deps bittensor # install bittensor
-    ```
\ No newline at end of file
+    ```
diff --git a/scripts/environments/apple_m1_environment.yml b/scripts/environments/apple_m1_environment.yml
index d789c31002..c18da7236a 100644
--- a/scripts/environments/apple_m1_environment.yml
+++ b/scripts/environments/apple_m1_environment.yml
@@ -211,7 +211,7 @@ dependencies:
       - pyarrow==12.0.1
       - pyasn1==0.5.0
       - pyasn1-modules==0.3.0
-      - pydantic==1.10.12
+      - pydantic==2.7.1
       - pydeck==0.8.1b0
       - pyinstrument==4.4.0
       - pympler==1.0.1
@@ -229,18 +229,19 @@ dependencies:
       - retry==0.9.2
       - rich==12.5.1
       - rsa==4.9
-      - scalecodec==1.2.7
+      - scalecodec==1.2.7  # scalecodec should not be changed unless first verifying compatibility with the subtensor's monkeypatching of scalecodec.RuntimeConfiguration.get_decoder_class
       - scikit-learn==1.2.2
       - scipy==1.10.1
       - sentencepiece==0.1.99
       - sentry-sdk==1.28.1
       - setproctitle==1.3.2
       - shortuuid==1.0.11
+      - shtab==1.6.5
       - smmap==5.0.0
       - sortedcontainers==2.4.0
       - soupsieve==2.4.1
       - sqlalchemy==2.0.19
-      - starlette==0.27.0
+      - starlette==0.37.2
       - streamlit==1.22.0
       - substrate-interface==1.5.2
       - tenacity==8.2.2
@@ -253,7 +254,7 @@ dependencies:
       - torchvision==0.15.2
       - tornado==6.3.3
       - tqdm==4.64.1
-      - typing-extensions==4.5.0
+      - typing-extensions==4.8.0
       - typing-inspect==0.8.0
       - tzlocal==5.0.1
       - uri-template==1.2.0
diff --git a/setup.py b/setup.py
index 8660101c04..49c419724a 100644
--- a/setup.py
+++ b/setup.py
@@ -42,6 +42,7 @@ def read_requirements(path):
 requirements = read_requirements("requirements/prod.txt")
 extra_requirements_dev = read_requirements("requirements/dev.txt")
 extra_requirements_cubit = read_requirements("requirements/cubit.txt")
+extra_requirements_torch = read_requirements("requirements/torch.txt")
 
 here = path.abspath(path.dirname(__file__))
 
@@ -70,10 +71,11 @@ def read_requirements(path):
     include_package_data=True,
     author_email="",
     license="MIT",
-    python_requires=">=3.8",
+    python_requires=">=3.9",
     install_requires=requirements,
     extras_require={
         "dev": extra_requirements_dev,
+        "torch": extra_requirements_torch,
     },
     scripts=["bin/btcli"],
     classifiers=[
@@ -83,9 +85,9 @@ def read_requirements(path):
         # Pick your license as you wish
         "License :: OSI Approved :: MIT License",
         "Programming Language :: Python :: 3 :: Only",
-        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Topic :: Scientific/Engineering",
         "Topic :: Scientific/Engineering :: Mathematics",
         "Topic :: Scientific/Engineering :: Artificial Intelligence",


Test patch:
diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py
index cdfa7e0bf1..fc9e8ad9d2 100644
--- a/tests/helpers/__init__.py
+++ b/tests/helpers/__init__.py
@@ -15,6 +15,7 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
+import os
 from .helpers import (
     _get_mock_coldkey,
     _get_mock_hotkey,
@@ -24,3 +25,8 @@
     MockConsole,
     __mock_wallet_factory__,
 )
+
+
+def is_running_in_circleci():
+    """Checks that tests are running in the app.circleci.com environment."""
+    return os.getenv("CIRCLECI") == "true"
diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py
index 5c6c9f377a..c20c905549 100644
--- a/tests/integration_tests/test_cli.py
+++ b/tests/integration_tests/test_cli.py
@@ -38,6 +38,7 @@
 from bittensor.mock import MockSubtensor
 from bittensor.wallet import wallet as Wallet
 from tests.helpers import (
+    is_running_in_circleci,
     MockConsole,
     _get_mock_keypair,
     _get_mock_wallet as generate_wallet,
@@ -104,214 +105,218 @@ def construct_config():
         return defaults
 
     def test_overview(self, _):
-        config = self.config
-        config.wallet.path = "/tmp/test_cli_test_overview"
-        config.wallet.name = "mock_wallet"
-        config.command = "wallet"
-        config.subcommand = "overview"
-        config.no_prompt = True
-        config.all = False
-        config.netuid = []  # Don't set, so it tries all networks.
+        if is_running_in_circleci():
+            config = self.config
+            config.wallet.path = "/tmp/test_cli_test_overview"
+            config.wallet.name = "mock_wallet"
+            config.command = "wallet"
+            config.subcommand = "overview"
+            config.no_prompt = True
+            config.all = False
+            config.netuid = []  # Don't set, so it tries all networks.
 
-        cli = bittensor.cli(config)
+            cli = bittensor.cli(config)
 
-        mock_hotkeys = ["hk0", "hk1", "hk2", "hk3", "hk4"]
+            mock_hotkeys = ["hk0", "hk1", "hk2", "hk3", "hk4"]
 
-        mock_coldkey_kp = _get_mock_keypair(0, self.id())
+            mock_coldkey_kp = _get_mock_keypair(0, self.id())
 
-        mock_wallets = [
-            SimpleNamespace(
-                name=config.wallet.name,
-                coldkey=mock_coldkey_kp,
-                coldkeypub=mock_coldkey_kp,
-                hotkey_str=hk,
-                hotkey=_get_mock_keypair(idx + 100, self.id()),
-                coldkeypub_file=MagicMock(
-                    exists_on_device=MagicMock(return_value=True)  # Wallet exists
-                ),
-            )
-            for idx, hk in enumerate(mock_hotkeys)
-        ]
+            mock_wallets = [
+                SimpleNamespace(
+                    name=config.wallet.name,
+                    coldkey=mock_coldkey_kp,
+                    coldkeypub=mock_coldkey_kp,
+                    hotkey_str=hk,
+                    hotkey=_get_mock_keypair(idx + 100, self.id()),
+                    coldkeypub_file=MagicMock(
+                        exists_on_device=MagicMock(return_value=True)  # Wallet exists
+                    ),
+                )
+                for idx, hk in enumerate(mock_hotkeys)
+            ]
 
-        mock_registrations = [
-            (1, mock_wallets[0]),
-            (1, mock_wallets[1]),
-            # (1, mock_wallets[2]), Not registered on netuid 1
-            (2, mock_wallets[0]),
-            # (2, mock_wallets[1]), Not registered on netuid 2
-            (2, mock_wallets[2]),
-            (3, mock_wallets[0]),
-            (3, mock_wallets[1]),
-            (3, mock_wallets[2]),  # All registered on netuid 3 (but hk3)
-            (3, mock_wallets[4]),  # hk4 is only on netuid 3
-        ]  # hk3 is not registered on any network
-
-        # Register each wallet to it's subnet.
-        print("Registering wallets to mock subtensor...")
-
-        for netuid, wallet in mock_registrations:
-            _ = _subtensor_mock.force_register_neuron(
-                netuid=netuid,
-                coldkey=wallet.coldkey.ss58_address,
-                hotkey=wallet.hotkey.ss58_address,
-            )
+            mock_registrations = [
+                (1, mock_wallets[0]),
+                (1, mock_wallets[1]),
+                # (1, mock_wallets[2]), Not registered on netuid 1
+                (2, mock_wallets[0]),
+                # (2, mock_wallets[1]), Not registered on netuid 2
+                (2, mock_wallets[2]),
+                (3, mock_wallets[0]),
+                (3, mock_wallets[1]),
+                (3, mock_wallets[2]),  # All registered on netuid 3 (but hk3)
+                (3, mock_wallets[4]),  # hk4 is only on netuid 3
+            ]  # hk3 is not registered on any network
+
+            # Register each wallet to it's subnet.
+            print("Registering wallets to mock subtensor...")
+
+            for netuid, wallet in mock_registrations:
+                _ = _subtensor_mock.force_register_neuron(
+                    netuid=netuid,
+                    coldkey=wallet.coldkey.ss58_address,
+                    hotkey=wallet.hotkey.ss58_address,
+                )
 
-        def mock_get_wallet(*args, **kwargs):
-            hk = kwargs.get("hotkey")
-            name_ = kwargs.get("name")
+            def mock_get_wallet(*args, **kwargs):
+                hk = kwargs.get("hotkey")
+                name_ = kwargs.get("name")
 
-            if not hk and kwargs.get("config"):
-                hk = kwargs.get("config").wallet.hotkey
-            if not name_ and kwargs.get("config"):
-                name_ = kwargs.get("config").wallet.name
+                if not hk and kwargs.get("config"):
+                    hk = kwargs.get("config").wallet.hotkey
+                if not name_ and kwargs.get("config"):
+                    name_ = kwargs.get("config").wallet.name
 
-            for wallet in mock_wallets:
-                if wallet.name == name_ and wallet.hotkey_str == hk:
-                    return wallet
-            else:
                 for wallet in mock_wallets:
-                    if wallet.name == name_:
+                    if wallet.name == name_ and wallet.hotkey_str == hk:
                         return wallet
                 else:
-                    return mock_wallets[0]
-
-        mock_console = MockConsole()
-        with patch(
-            "bittensor.commands.overview.get_hotkey_wallets_for_wallet"
-        ) as mock_get_all_wallets:
-            mock_get_all_wallets.return_value = mock_wallets
-            with patch("bittensor.wallet") as mock_create_wallet:
-                mock_create_wallet.side_effect = mock_get_wallet
-                with patch("bittensor.__console__", mock_console):
-                    cli.run()
-
-                    # Check that the overview was printed.
-                    self.assertIsNotNone(mock_console.captured_print)
+                    for wallet in mock_wallets:
+                        if wallet.name == name_:
+                            return wallet
+                    else:
+                        return mock_wallets[0]
 
-                    output_no_syntax = mock_console.remove_rich_syntax(
-                        mock_console.captured_print
-                    )
+            mock_console = MockConsole()
+            with patch(
+                "bittensor.commands.overview.get_hotkey_wallets_for_wallet"
+            ) as mock_get_all_wallets:
+                mock_get_all_wallets.return_value = mock_wallets
+                with patch("bittensor.wallet") as mock_create_wallet:
+                    mock_create_wallet.side_effect = mock_get_wallet
+                    with patch("bittensor.__console__", mock_console):
+                        cli.run()
+
+                        # Check that the overview was printed.
+                        self.assertIsNotNone(mock_console.captured_print)
+
+                        output_no_syntax = mock_console.remove_rich_syntax(
+                            mock_console.captured_print
+                        )
 
-                    # Check that each subnet was printed.
-                    self.assertIn("Subnet: 1", output_no_syntax)
-                    self.assertIn("Subnet: 2", output_no_syntax)
-                    self.assertIn("Subnet: 3", output_no_syntax)
+                        # Check that each subnet was printed.
+                        self.assertIn("Subnet: 1", output_no_syntax)
+                        self.assertIn("Subnet: 2", output_no_syntax)
+                        self.assertIn("Subnet: 3", output_no_syntax)
 
-                    # Check that only registered hotkeys are printed once for each subnet.
-                    for wallet in mock_wallets:
-                        expected = [
-                            wallet.hotkey_str for _, wallet in mock_registrations
-                        ].count(wallet.hotkey_str)
-                        occurrences = output_no_syntax.count(wallet.hotkey_str)
-                        self.assertEqual(occurrences, expected)
+                        # Check that only registered hotkeys are printed once for each subnet.
+                        for wallet in mock_wallets:
+                            expected = [
+                                wallet.hotkey_str for _, wallet in mock_registrations
+                            ].count(wallet.hotkey_str)
+                            occurrences = output_no_syntax.count(wallet.hotkey_str)
+                            self.assertEqual(occurrences, expected)
 
-                    # Check that unregistered hotkeys are not printed.
-                    for wallet in mock_wallets:
-                        if wallet not in [w for _, w in mock_registrations]:
-                            self.assertNotIn(wallet.hotkey_str, output_no_syntax)
+                        # Check that unregistered hotkeys are not printed.
+                        for wallet in mock_wallets:
+                            if wallet not in [w for _, w in mock_registrations]:
+                                self.assertNotIn(wallet.hotkey_str, output_no_syntax)
 
     def test_overview_not_in_first_subnet(self, _):
-        config = self.config
-        config.wallet.path = "/tmp/test_cli_test_overview"
-        config.wallet.name = "mock_wallet"
-        config.command = "wallet"
-        config.subcommand = "overview"
-        config.no_prompt = True
-        config.all = False
-        config.netuid = []  # Don't set, so it tries all networks.
+        if is_running_in_circleci():
+            config = self.config
+            config.wallet.path = "/tmp/test_cli_test_overview"
+            config.wallet.name = "mock_wallet"
+            config.command = "wallet"
+            config.subcommand = "overview"
+            config.no_prompt = True
+            config.all = False
+            config.netuid = []  # Don't set, so it tries all networks.
 
-        cli = bittensor.cli(config)
+            cli = bittensor.cli(config)
 
-        mock_hotkeys = ["hk0", "hk1", "hk2", "hk3", "hk4"]
+            mock_hotkeys = ["hk0", "hk1", "hk2", "hk3", "hk4"]
 
-        mock_coldkey_kp = _get_mock_keypair(0, self.id())
+            mock_coldkey_kp = _get_mock_keypair(0, self.id())
 
-        mock_wallets = [
-            SimpleNamespace(
-                name=config.wallet.name,
-                coldkey=mock_coldkey_kp,
-                coldkeypub=mock_coldkey_kp,
-                hotkey_str=hk,
-                hotkey=_get_mock_keypair(idx + 100, self.id()),
-                coldkeypub_file=MagicMock(
-                    exists_on_device=MagicMock(return_value=True)  # Wallet exists
-                ),
-            )
-            for idx, hk in enumerate(mock_hotkeys)
-        ]
+            mock_wallets = [
+                SimpleNamespace(
+                    name=config.wallet.name,
+                    coldkey=mock_coldkey_kp,
+                    coldkeypub=mock_coldkey_kp,
+                    hotkey_str=hk,
+                    hotkey=_get_mock_keypair(idx + 100, self.id()),
+                    coldkeypub_file=MagicMock(
+                        exists_on_device=MagicMock(return_value=True)  # Wallet exists
+                    ),
+                )
+                for idx, hk in enumerate(mock_hotkeys)
+            ]
 
-        mock_registrations = [
-            # No registrations in subnet 1 or 2
-            (3, mock_wallets[4])  # hk4 is on netuid 3
-        ]
+            mock_registrations = [
+                # No registrations in subnet 1 or 2
+                (3, mock_wallets[4])  # hk4 is on netuid 3
+            ]
 
-        # Register each wallet to it's subnet
-        print("Registering mock wallets to subnets...")
+            # Register each wallet to it's subnet
+            print("Registering mock wallets to subnets...")
 
-        for netuid, wallet in mock_registrations:
-            print(
-                "Registering wallet {} to subnet {}".format(wallet.hotkey_str, netuid)
-            )
-            _ = _subtensor_mock.force_register_neuron(
-                netuid=netuid,
-                coldkey=wallet.coldkey.ss58_address,
-                hotkey=wallet.hotkey.ss58_address,
-            )
+            for netuid, wallet in mock_registrations:
+                print(
+                    "Registering wallet {} to subnet {}".format(
+                        wallet.hotkey_str, netuid
+                    )
+                )
+                _ = _subtensor_mock.force_register_neuron(
+                    netuid=netuid,
+                    coldkey=wallet.coldkey.ss58_address,
+                    hotkey=wallet.hotkey.ss58_address,
+                )
 
-        def mock_get_wallet(*args, **kwargs):
-            hk = kwargs.get("hotkey")
-            name_ = kwargs.get("name")
+            def mock_get_wallet(*args, **kwargs):
+                hk = kwargs.get("hotkey")
+                name_ = kwargs.get("name")
 
-            if not hk and kwargs.get("config"):
-                hk = kwargs.get("config").wallet.hotkey
-            if not name_ and kwargs.get("config"):
-                name_ = kwargs.get("config").wallet.name
+                if not hk and kwargs.get("config"):
+                    hk = kwargs.get("config").wallet.hotkey
+                if not name_ and kwargs.get("config"):
+                    name_ = kwargs.get("config").wallet.name
 
-            for wallet in mock_wallets:
-                if wallet.name == name_ and wallet.hotkey_str == hk:
-                    return wallet
-            else:
                 for wallet in mock_wallets:
-                    if wallet.name == name_:
+                    if wallet.name == name_ and wallet.hotkey_str == hk:
                         return wallet
                 else:
-                    return mock_wallets[0]
-
-        mock_console = MockConsole()
-        with patch(
-            "bittensor.commands.overview.get_hotkey_wallets_for_wallet"
-        ) as mock_get_all_wallets:
-            mock_get_all_wallets.return_value = mock_wallets
-            with patch("bittensor.wallet") as mock_create_wallet:
-                mock_create_wallet.side_effect = mock_get_wallet
-                with patch("bittensor.__console__", mock_console):
-                    cli.run()
-
-                    # Check that the overview was printed.
-                    self.assertIsNotNone(mock_console.captured_print)
-
-                    output_no_syntax = mock_console.remove_rich_syntax(
-                        mock_console.captured_print
-                    )
-
-                    # Check that each subnet was printed except subnet 1 and 2.
-                    # Subnet 1 and 2 are not printed because no wallet is registered to them.
-                    self.assertNotIn("Subnet: 1", output_no_syntax)
-                    self.assertNotIn("Subnet: 2", output_no_syntax)
-                    self.assertIn("Subnet: 3", output_no_syntax)
-
-                    # Check that only registered hotkeys are printed once for each subnet.
                     for wallet in mock_wallets:
-                        expected = [
-                            wallet.hotkey_str for _, wallet in mock_registrations
-                        ].count(wallet.hotkey_str)
-                        occurrences = output_no_syntax.count(wallet.hotkey_str)
-                        self.assertEqual(occurrences, expected)
+                        if wallet.name == name_:
+                            return wallet
+                    else:
+                        return mock_wallets[0]
 
-                    # Check that unregistered hotkeys are not printed.
-                    for wallet in mock_wallets:
-                        if wallet not in [w for _, w in mock_registrations]:
-                            self.assertNotIn(wallet.hotkey_str, output_no_syntax)
+            mock_console = MockConsole()
+            with patch(
+                "bittensor.commands.overview.get_hotkey_wallets_for_wallet"
+            ) as mock_get_all_wallets:
+                mock_get_all_wallets.return_value = mock_wallets
+                with patch("bittensor.wallet") as mock_create_wallet:
+                    mock_create_wallet.side_effect = mock_get_wallet
+                    with patch("bittensor.__console__", mock_console):
+                        cli.run()
+
+                        # Check that the overview was printed.
+                        self.assertIsNotNone(mock_console.captured_print)
+
+                        output_no_syntax = mock_console.remove_rich_syntax(
+                            mock_console.captured_print
+                        )
+
+                        # Check that each subnet was printed except subnet 1 and 2.
+                        # Subnet 1 and 2 are not printed because no wallet is registered to them.
+                        self.assertNotIn("Subnet: 1", output_no_syntax)
+                        self.assertNotIn("Subnet: 2", output_no_syntax)
+                        self.assertIn("Subnet: 3", output_no_syntax)
+
+                        # Check that only registered hotkeys are printed once for each subnet.
+                        for wallet in mock_wallets:
+                            expected = [
+                                wallet.hotkey_str for _, wallet in mock_registrations
+                            ].count(wallet.hotkey_str)
+                            occurrences = output_no_syntax.count(wallet.hotkey_str)
+                            self.assertEqual(occurrences, expected)
+
+                        # Check that unregistered hotkeys are not printed.
+                        for wallet in mock_wallets:
+                            if wallet not in [w for _, w in mock_registrations]:
+                                self.assertNotIn(wallet.hotkey_str, output_no_syntax)
 
     def test_overview_with_hotkeys_config(self, _):
         config = self.config
@@ -2083,6 +2088,8 @@ def test_register(self, _):
             self.assertTrue(registered)
 
     def test_pow_register(self, _):
+        # Not the best way to do this, but I need to finish these tests, and unittest doesn't make this
+        # as simple as pytest
         config = self.config
         config.command = "subnets"
         config.subcommand = "pow_register"
@@ -2249,10 +2256,16 @@ class TestCLIWithNetworkUsingArgs(unittest.TestCase):
     Test the CLI by passing args directly to the bittensor.cli factory
     """
 
-    def test_list_delegates(self, _):
+    @unittest.mock.patch.object(MockSubtensor, "get_delegates")
+    def test_list_delegates(self, mocked_get_delegates, _):
+        # Call
         cli = bittensor.cli(args=["root", "list_delegates"])
         cli.run()
 
+        # Assertions
+        # make sure get_delegates called once without previous state (current only)
+        self.assertEqual(mocked_get_delegates.call_count, 2)
+
     def test_list_subnets(self, _):
         cli = bittensor.cli(
             args=[
@@ -2354,18 +2367,11 @@ def test_delegate(self, _):
         self.assertAlmostEqual(new_balance.tao, old_balance.tao - 10.0, delta=1e-6)
 
 
-# Test directory for creating mock wallets
-TEST_DIR = "/tmp/test_bittensor_wallets"
-
-
 @pytest.fixture(scope="function")
-def setup_wallets():
-    # Arrange: Create a temporary directory to simulate wallet paths
-    if not os.path.exists(TEST_DIR):
-        os.makedirs(TEST_DIR)
-    yield
-    # Teardown: Remove the temporary directory after tests
-    shutil.rmtree(TEST_DIR)
+def wallets_dir_path(tmp_path):
+    wallets_dir = tmp_path / "wallets"
+    wallets_dir.mkdir()
+    yield wallets_dir
 
 
 @pytest.mark.parametrize(
@@ -2381,15 +2387,14 @@ def setup_wallets():
     ],
 )
 def test_get_coldkey_wallets_for_path(
-    test_id, wallet_names, expected_wallet_count, setup_wallets
+    test_id, wallet_names, expected_wallet_count, wallets_dir_path
 ):
     # Arrange: Create mock wallet directories
     for name in wallet_names:
-        wallet_path = os.path.join(TEST_DIR, name)
-        os.makedirs(wallet_path)
+        (wallets_dir_path / name).mkdir()
 
     # Act: Call the function with the test directory
-    wallets = _get_coldkey_wallets_for_path(TEST_DIR)
+    wallets = _get_coldkey_wallets_for_path(str(wallets_dir_path))
 
     # Assert: Check if the correct number of wallet objects are returned
     assert len(wallets) == expected_wallet_count
@@ -2530,11 +2535,6 @@ def test_set_identity_command(
             assert mock_exit.call_count == 0
 
 
-TEST_DIR = "/tmp/test_bittensor_wallets"
-if not os.path.exists(TEST_DIR):
-    os.makedirs(TEST_DIR)
-
-
 @pytest.fixture
 def setup_files(tmp_path):
     def _setup_files(files):
@@ -2576,11 +2576,5 @@ def test_get_coldkey_ss58_addresses_for_path(
     ), f"Test ID: {test_id} failed. Expected {expected}, got {result}"
 
 
-# Cleanup after tests
-def teardown_module(module):
-    with contextlib.suppress(FileNotFoundError):
-        shutil.rmtree(TEST_DIR)
-
-
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/integration_tests/test_metagraph_integration.py b/tests/integration_tests/test_metagraph_integration.py
index 47ab8abe55..5dbb9ddfc1 100644
--- a/tests/integration_tests/test_metagraph_integration.py
+++ b/tests/integration_tests/test_metagraph_integration.py
@@ -17,9 +17,10 @@
 # DEALINGS IN THE SOFTWARE.
 
 import bittensor
-from bittensor.mock import MockSubtensor
 import torch
-import pytest
+import os
+from bittensor.mock import MockSubtensor
+from bittensor.metagraph import METAGRAPH_STATE_DICT_NDARRAY_KEYS, get_save_dir
 
 _subtensor_mock: MockSubtensor = MockSubtensor()
 
@@ -56,6 +57,23 @@ def test_load_sync_save(self):
         self.metagraph.load()
         self.metagraph.save()
 
+    def test_load_sync_save_from_torch(self):
+        self.metagraph.sync(lite=True, subtensor=self.sub)
+
+        def deprecated_save_torch(metagraph):
+            save_directory = get_save_dir(metagraph.network, metagraph.netuid)
+            os.makedirs(save_directory, exist_ok=True)
+            graph_filename = save_directory + f"/block-{metagraph.block.item()}.pt"
+            state_dict = metagraph.state_dict()
+            for key in METAGRAPH_STATE_DICT_NDARRAY_KEYS:
+                state_dict[key] = torch.nn.Parameter(
+                    torch.tensor(state_dict[key]), requires_grad=False
+                )
+            torch.save(state_dict, graph_filename)
+
+        deprecated_save_torch(self.metagraph)
+        self.metagraph.load()
+
     def test_state_dict(self):
         self.metagraph.load()
         state = self.metagraph.state_dict()
@@ -94,8 +112,3 @@ def test_properties(self):
         metagraph.D
         metagraph.B
         metagraph.W
-
-    def test_parameters(self):
-        params = list(self.metagraph.parameters())
-        assert len(params) > 0
-        assert isinstance(params[0], torch.nn.parameter.Parameter)
diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py
index b4b6e905e5..845a73ee7d 100644
--- a/tests/integration_tests/test_subtensor_integration.py
+++ b/tests/integration_tests/test_subtensor_integration.py
@@ -16,9 +16,9 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-
 import random
 import socket
+import os
 import unittest
 from queue import Empty as QueueEmpty
 from unittest.mock import MagicMock, patch
@@ -548,7 +548,7 @@ def test_registration_failed(self):
             self.assertEqual(mock_create_pow.call_count, 3)
 
     def test_registration_stale_then_continue(self):
-        # verifty that after a stale solution, the solve will continue without exiting
+        # verify that after a stale solution, the solve will continue without exiting
 
         class ExitEarly(Exception):
             pass
diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py
new file mode 100644
index 0000000000..90b6f25748
--- /dev/null
+++ b/tests/unit_tests/conftest.py
@@ -0,0 +1,13 @@
+import pytest
+from aioresponses import aioresponses
+
+
+@pytest.fixture
+def force_legacy_torch_compat_api(monkeypatch):
+    monkeypatch.setenv("USE_TORCH", "1")
+
+
+@pytest.fixture
+def mock_aioresponse():
+    with aioresponses() as m:
+        yield m
diff --git a/tests/unit_tests/extrinsics/test_delegation.py b/tests/unit_tests/extrinsics/test_delegation.py
new file mode 100644
index 0000000000..9a321dbe64
--- /dev/null
+++ b/tests/unit_tests/extrinsics/test_delegation.py
@@ -0,0 +1,459 @@
+import pytest
+from unittest.mock import MagicMock, patch
+from bittensor.subtensor import subtensor as Subtensor
+from bittensor.wallet import wallet as Wallet
+from bittensor.utils.balance import Balance
+from bittensor.extrinsics.delegation import (
+    nominate_extrinsic,
+    delegate_extrinsic,
+    undelegate_extrinsic,
+)
+from bittensor.errors import (
+    NominationError,
+    NotDelegateError,
+    NotRegisteredError,
+    StakeError,
+)
+
+
+@pytest.fixture
+def mock_subtensor():
+    mock = MagicMock(spec=Subtensor)
+    mock.network = "magic_mock"
+    return mock
+
+
+@pytest.fixture
+def mock_wallet():
+    mock = MagicMock(spec=Wallet)
+    mock.hotkey.ss58_address = "fake_hotkey_address"
+    mock.coldkey.ss58_address = "fake_coldkey_address"
+    mock.coldkey = MagicMock()
+    mock.hotkey = MagicMock()
+    mock.name = "fake_wallet_name"
+    mock.hotkey_str = "fake_hotkey_str"
+    return mock
+
+
+@pytest.mark.parametrize(
+    "already_delegate, nomination_success, raises_exception, expected_result",
+    [
+        (False, True, None, True),  # Successful nomination
+        (True, None, None, False),  # Already a delegate
+        (False, None, NominationError, False),  # Failure - Nomination error
+        (False, None, ValueError, False),  # Failure - ValueError
+    ],
+    ids=[
+        "success-nomination-done",
+        "failure-already-delegate",
+        "failure-nomination-error",
+        "failure-value-error",
+    ],
+)
+def test_nominate_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    already_delegate,
+    nomination_success,
+    raises_exception,
+    expected_result,
+):
+    # Arrange
+    with patch.object(
+        mock_subtensor, "is_hotkey_delegate", return_value=already_delegate
+    ), patch.object(
+        mock_subtensor, "_do_nominate", return_value=nomination_success
+    ) as mock_nominate:
+        if raises_exception:
+            mock_subtensor._do_nominate.side_effect = raises_exception
+
+        # Act
+        result = nominate_extrinsic(
+            subtensor=mock_subtensor,
+            wallet=mock_wallet,
+            wait_for_finalization=False,
+            wait_for_inclusion=True,
+        )
+        # Assert
+        assert result == expected_result
+
+        if not already_delegate and nomination_success is not None:
+            mock_nominate.assert_called_once_with(
+                wallet=mock_wallet, wait_for_inclusion=True, wait_for_finalization=False
+            )
+
+
+@pytest.mark.parametrize(
+    "wait_for_inclusion, wait_for_finalization, is_delegate, prompt_response, stake_amount, balance_sufficient, transaction_success, raises_error, expected_result, delegate_called",
+    [
+        (True, False, True, True, 100, True, True, None, True, True),  # Success case
+        (
+            False,
+            False,
+            True,
+            True,
+            100,
+            True,
+            True,
+            None,
+            True,
+            True,
+        ),  # Success case - no wait
+        (
+            True,
+            False,
+            True,
+            True,
+            None,
+            True,
+            True,
+            None,
+            True,
+            True,
+        ),  # Success case - all stake
+        (
+            True,
+            False,
+            True,
+            True,
+            0.000000100,
+            True,
+            True,
+            None,
+            True,
+            True,
+        ),  # Success case - below cutoff threshold
+        (
+            True,
+            False,
+            True,
+            True,
+            Balance.from_tao(1),
+            True,
+            True,
+            None,
+            True,
+            True,
+        ),  # Success case - from Tao
+        (
+            True,
+            False,
+            False,
+            None,
+            100,
+            True,
+            False,
+            NotDelegateError,
+            False,
+            False,
+        ),  # Not a delegate error
+        (
+            True,
+            False,
+            True,
+            True,
+            200,
+            False,
+            False,
+            None,
+            False,
+            False,
+        ),  # Insufficient balance
+        (
+            True,
+            False,
+            True,
+            False,
+            100,
+            True,
+            True,
+            None,
+            False,
+            False,
+        ),  # User declines prompt
+        (
+            True,
+            False,
+            True,
+            True,
+            100,
+            True,
+            False,
+            None,
+            False,
+            True,
+        ),  # Transaction fails
+        (
+            True,
+            False,
+            True,
+            True,
+            100,
+            True,
+            False,
+            NotRegisteredError,
+            False,
+            True,
+        ),  # Raises a NotRegisteredError
+        (
+            True,
+            False,
+            True,
+            True,
+            100,
+            True,
+            False,
+            StakeError,
+            False,
+            True,
+        ),  # Raises a StakeError
+    ],
+    ids=[
+        "success-delegate",
+        "success-no-wait",
+        "success-all-stake",
+        "success-below-existential-threshold",
+        "success-from-tao",
+        "failure-not-delegate",
+        "failure-low-balance",
+        "failure-prompt-declined",
+        "failure-transaction-failed",
+        "failure-NotRegisteredError",
+        "failure-StakeError",
+    ],
+)
+def test_delegate_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    wait_for_inclusion,
+    wait_for_finalization,
+    is_delegate,
+    prompt_response,
+    stake_amount,
+    balance_sufficient,
+    transaction_success,
+    raises_error,
+    expected_result,
+    delegate_called,
+):
+    # Arrange
+    wallet_balance = Balance.from_tao(500)
+    wallet_insufficient_balance = Balance.from_tao(0.002)
+
+    with patch("rich.prompt.Confirm.ask", return_value=prompt_response), patch.object(
+        mock_subtensor,
+        "get_balance",
+        return_value=wallet_balance
+        if balance_sufficient
+        else wallet_insufficient_balance,
+    ), patch.object(
+        mock_subtensor, "is_hotkey_delegate", return_value=is_delegate
+    ), patch.object(
+        mock_subtensor, "_do_delegation", return_value=transaction_success
+    ) as mock_delegate:
+        if raises_error:
+            mock_delegate.side_effect = raises_error
+
+        # Act
+        if raises_error == NotDelegateError:
+            with pytest.raises(raises_error):
+                result = delegate_extrinsic(
+                    subtensor=mock_subtensor,
+                    wallet=mock_wallet,
+                    delegate_ss58=mock_wallet.hotkey.ss58_address,
+                    amount=stake_amount,
+                    wait_for_inclusion=wait_for_inclusion,
+                    wait_for_finalization=wait_for_finalization,
+                    prompt=True,
+                )
+        else:
+            result = delegate_extrinsic(
+                subtensor=mock_subtensor,
+                wallet=mock_wallet,
+                delegate_ss58=mock_wallet.hotkey.ss58_address,
+                amount=stake_amount,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+                prompt=True,
+            )
+            # Assert
+            assert result == expected_result
+
+        if delegate_called:
+            if stake_amount is None:
+                called_stake_amount = wallet_balance
+            elif isinstance(stake_amount, Balance):
+                called_stake_amount = stake_amount
+            else:
+                called_stake_amount = Balance.from_tao(stake_amount)
+
+            if called_stake_amount > Balance.from_rao(1000):
+                called_stake_amount -= Balance.from_rao(1000)
+
+            mock_delegate.assert_called_once_with(
+                wallet=mock_wallet,
+                delegate_ss58=mock_wallet.hotkey.ss58_address,
+                amount=called_stake_amount,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+            )
+
+
+@pytest.mark.parametrize(
+    "wait_for_inclusion, wait_for_finalization, is_delegate, prompt_response, unstake_amount, current_stake, transaction_success, raises_error, expected_result",
+    [
+        (True, False, True, True, 50, 100, True, None, True),  # Success case
+        (False, False, True, True, 50, 100, True, None, True),  # Success case - no wait
+        (
+            False,
+            False,
+            True,
+            True,
+            Balance.from_tao(1),
+            100,
+            True,
+            None,
+            True,
+        ),  # Success case - from tao
+        (True, False, True, True, None, 100, True, None, True),  # Success - unstake all
+        (
+            True,
+            False,
+            True,
+            True,
+            1000,
+            1000,
+            False,
+            None,
+            False,
+        ),  # Failure - transaction fails
+        (
+            True,
+            False,
+            False,
+            None,
+            100,
+            120,
+            True,
+            NotDelegateError,
+            False,
+        ),  # Not a delegate
+        (True, False, True, False, 100, 111, True, None, False),  # User declines prompt
+        (
+            True,
+            False,
+            True,
+            True,
+            100,
+            90,
+            True,
+            None,
+            False,
+        ),  # Insufficient stake to unstake
+        (
+            True,
+            False,
+            True,
+            True,
+            100,
+            100,
+            False,
+            StakeError,
+            False,
+        ),  # StakeError raised
+        (
+            True,
+            False,
+            True,
+            True,
+            100,
+            100,
+            False,
+            NotRegisteredError,
+            False,
+        ),  # NotRegisteredError raised
+    ],
+    ids=[
+        "success-undelegate",
+        "success-undelegate-no-wait",
+        "success-from-tao",
+        "success-undelegate-all",
+        "failure-transaction-failed",
+        "failure-NotDelegateError",
+        "failure-prompt-declined",
+        "failure-insufficient-stake",
+        "failure--StakeError",
+        "failure-NotRegisteredError",
+    ],
+)
+def test_undelegate_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    wait_for_inclusion,
+    wait_for_finalization,
+    is_delegate,
+    prompt_response,
+    unstake_amount,
+    current_stake,
+    transaction_success,
+    raises_error,
+    expected_result,
+):
+    # Arrange
+    wallet_balance = Balance.from_tao(500)
+
+    with patch("rich.prompt.Confirm.ask", return_value=prompt_response), patch.object(
+        mock_subtensor, "is_hotkey_delegate", return_value=is_delegate
+    ), patch.object(
+        mock_subtensor, "get_balance", return_value=wallet_balance
+    ), patch.object(
+        mock_subtensor,
+        "get_stake_for_coldkey_and_hotkey",
+        return_value=Balance.from_tao(current_stake),
+    ), patch.object(
+        mock_subtensor, "_do_undelegation", return_value=transaction_success
+    ) as mock_undelegate:
+        if raises_error:
+            mock_undelegate.side_effect = raises_error
+
+        # Act
+        if raises_error == NotDelegateError:
+            with pytest.raises(raises_error):
+                result = undelegate_extrinsic(
+                    subtensor=mock_subtensor,
+                    wallet=mock_wallet,
+                    delegate_ss58=mock_wallet.hotkey.ss58_address,
+                    amount=unstake_amount,
+                    wait_for_inclusion=wait_for_inclusion,
+                    wait_for_finalization=wait_for_finalization,
+                    prompt=True,
+                )
+        else:
+            result = undelegate_extrinsic(
+                subtensor=mock_subtensor,
+                wallet=mock_wallet,
+                delegate_ss58=mock_wallet.hotkey.ss58_address,
+                amount=unstake_amount,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+                prompt=True,
+            )
+
+            # Assert
+            assert result == expected_result
+
+        if expected_result and prompt_response:
+            if unstake_amount is None:
+                called_unstake_amount = Balance.from_tao(current_stake)
+            elif isinstance(unstake_amount, Balance):
+                called_unstake_amount = unstake_amount
+            else:
+                called_unstake_amount = Balance.from_tao(unstake_amount)
+
+            mock_undelegate.assert_called_once_with(
+                wallet=mock_wallet,
+                delegate_ss58=mock_wallet.hotkey.ss58_address,
+                amount=called_unstake_amount,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+            )
diff --git a/tests/unit_tests/extrinsics/test_network.py b/tests/unit_tests/extrinsics/test_network.py
index 160348c3e6..a11f53111f 100644
--- a/tests/unit_tests/extrinsics/test_network.py
+++ b/tests/unit_tests/extrinsics/test_network.py
@@ -2,6 +2,10 @@
 from unittest.mock import MagicMock, patch
 from bittensor.subtensor import subtensor as Subtensor
 from bittensor.wallet import wallet as Wallet
+from bittensor.extrinsics.network import (
+    set_hyperparameter_extrinsic,
+    register_subnetwork_extrinsic,
+)
 
 
 # Mock the bittensor and related modules to avoid real network calls and wallet operations
@@ -10,6 +14,8 @@ def mock_subtensor():
     subtensor = MagicMock(spec=Subtensor)
     subtensor.get_balance.return_value = 100
     subtensor.get_subnet_burn_cost.return_value = 10
+    subtensor.substrate = MagicMock()
+    subtensor.substrate.get_block_hash = MagicMock(return_value="0x" + "0" * 64)
     return subtensor
 
 
@@ -21,6 +27,13 @@ def mock_wallet():
     return wallet
 
 
+@pytest.fixture
+def mock_other_owner_wallet():
+    wallet = MagicMock(spec=Wallet)
+    wallet.coldkeypub.ss58_address = "fake_other_owner"
+    return wallet
+
+
 @pytest.mark.parametrize(
     "test_id, wait_for_inclusion, wait_for_finalization, prompt, expected",
     [
@@ -40,15 +53,9 @@ def test_register_subnetwork_extrinsic_happy_path(
     expected,
 ):
     # Arrange
-    mock_subtensor.substrate = MagicMock(
-        get_block_hash=MagicMock(return_value="0x" + "0" * 64),
-        submit_extrinsic=MagicMock(),
-    )
     mock_subtensor.substrate.submit_extrinsic.return_value.is_success = True
 
     # Act
-    from bittensor.extrinsics.network import register_subnetwork_extrinsic
-
     result = register_subnetwork_extrinsic(
         mock_subtensor, mock_wallet, wait_for_inclusion, wait_for_finalization, prompt
     )
@@ -81,12 +88,70 @@ def test_register_subnetwork_extrinsic_edge_cases(
     mock_subtensor.get_balance.return_value = balance
     mock_subtensor.get_subnet_burn_cost.return_value = burn_cost
     monkeypatch.setattr("rich.prompt.Confirm.ask", lambda x: prompt_input)
-    mock_subtensor.substrate = MagicMock()
 
     # Act
-    from bittensor.extrinsics.network import register_subnetwork_extrinsic
-
     result = register_subnetwork_extrinsic(mock_subtensor, mock_wallet, prompt=True)
 
     # Assert
     assert result == expected
+
+
+@pytest.mark.parametrize(
+    "netuid, parameter, value, is_owner, wait_for_inclusion, wait_for_finalization, prompt, extrinsic_success, expected_result",
+    [
+        # Success - no wait
+        (1, "serving_rate_limit", 49, True, False, False, False, True, True),
+        # Success - with wait
+        (1, "serving_rate_limit", 50, True, True, True, False, True, True),
+        # Failure - wallet doesn't own subnet
+        (1, "serving_rate_limit", 50, False, True, True, False, True, False),
+        # Failure - invalid hyperparameter
+        (1, None, 50, True, True, False, False, False, False),
+    ],
+    ids=[
+        "success-no-wait",
+        "success-with-wait",
+        "failure-not-owner",
+        "failure-invalid-hyperparameter",
+    ],
+)
+def test_set_hyperparameter_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    mock_other_owner_wallet,
+    netuid,
+    parameter,
+    value,
+    is_owner,
+    wait_for_inclusion,
+    wait_for_finalization,
+    prompt,
+    extrinsic_success,
+    expected_result,
+):
+    # Arrange
+    with patch.object(
+        mock_subtensor,
+        "get_subnet_owner",
+        return_value=mock_wallet.coldkeypub.ss58_address
+        if is_owner
+        else mock_other_owner_wallet.coldkeypub.ss58_address,
+    ), patch.object(
+        mock_subtensor.substrate,
+        "submit_extrinsic",
+        return_value=MagicMock(is_success=extrinsic_success),
+    ):
+        # Act
+        result = set_hyperparameter_extrinsic(
+            subtensor=mock_subtensor,
+            wallet=mock_wallet,
+            netuid=netuid,
+            parameter=parameter,
+            value=value,
+            wait_for_inclusion=wait_for_inclusion,
+            wait_for_finalization=wait_for_finalization,
+            prompt=prompt,
+        )
+
+        # Assert
+        assert result == expected_result
diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py
index f20d037b96..bad8552b17 100644
--- a/tests/unit_tests/extrinsics/test_registration.py
+++ b/tests/unit_tests/extrinsics/test_registration.py
@@ -102,7 +102,9 @@ def test_run_faucet_extrinsic_happy_path(
         # Assert
         if isinstance(result, tuple):
             assert result[0] == expected
-            mock_subtensor.substrate.submit_extrinsic.assert_called()
+            if result[0] is True:
+                # Checks only if successful
+                mock_subtensor.substrate.submit_extrinsic.assert_called()
         else:
             assert result == expected
         mock_subtensor.get_balance.assert_called_with("mock_address")
@@ -140,7 +142,7 @@ def test_run_faucet_extrinsic_edge_cases(
         )
 
         # Assert
-        assert result == expected
+        assert result[0] == expected
 
 
 @pytest.mark.parametrize(
diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py
new file mode 100644
index 0000000000..84132bb60d
--- /dev/null
+++ b/tests/unit_tests/extrinsics/test_root.py
@@ -0,0 +1,308 @@
+import pytest
+from unittest.mock import MagicMock, patch
+from bittensor.subtensor import subtensor as Subtensor
+from bittensor.extrinsics.root import (
+    root_register_extrinsic,
+    set_root_weights_extrinsic,
+)
+
+
+@pytest.fixture
+def mock_subtensor():
+    mock = MagicMock(spec=Subtensor)
+    mock.network = "magic_mock"
+    return mock
+
+
+@pytest.fixture
+def mock_wallet():
+    mock = MagicMock()
+    mock.hotkey.ss58_address = "fake_hotkey_address"
+    return mock
+
+
+@pytest.mark.parametrize(
+    "wait_for_inclusion, wait_for_finalization, hotkey_registered, registration_success, prompt, user_response, expected_result",
+    [
+        (
+            False,
+            True,
+            [True, None],
+            True,
+            True,
+            True,
+            True,
+        ),  # Already registered after attempt
+        (
+            False,
+            True,
+            [False, True],
+            True,
+            True,
+            True,
+            True,
+        ),  # Registration succeeds with user confirmation
+        (False, True, [False, False], False, False, None, None),  # Registration fails
+        (
+            False,
+            True,
+            [False, False],
+            True,
+            False,
+            None,
+            None,
+        ),  # Registration succeeds but neuron not found
+        (
+            False,
+            True,
+            [False, False],
+            True,
+            True,
+            False,
+            False,
+        ),  # User declines registration
+    ],
+    ids=[
+        "success-already-registered",
+        "success-registration-succeeds",
+        "failure-registration-failed",
+        "failure-neuron-not-found",
+        "failure-prompt-declined",
+    ],
+)
+def test_root_register_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    wait_for_inclusion,
+    wait_for_finalization,
+    hotkey_registered,
+    registration_success,
+    prompt,
+    user_response,
+    expected_result,
+):
+    # Arrange
+    mock_subtensor.is_hotkey_registered.side_effect = hotkey_registered
+
+    with patch.object(
+        mock_subtensor,
+        "_do_root_register",
+        return_value=(registration_success, "Error registering"),
+    ) as mock_register, patch("rich.prompt.Confirm.ask", return_value=user_response):
+        # Act
+        result = root_register_extrinsic(
+            subtensor=mock_subtensor,
+            wallet=mock_wallet,
+            wait_for_inclusion=wait_for_inclusion,
+            wait_for_finalization=wait_for_finalization,
+            prompt=prompt,
+        )
+        # Assert
+        assert result == expected_result
+
+        if not hotkey_registered[0] and user_response:
+            mock_register.assert_called_once()
+
+
+@pytest.mark.parametrize(
+    "wait_for_inclusion, wait_for_finalization, netuids, weights, prompt, user_response, expected_success",
+    [
+        (True, False, [1, 2], [0.5, 0.5], True, True, True),  # Success - weights set
+        (
+            False,
+            False,
+            [1, 2],
+            [0.5, 0.5],
+            False,
+            None,
+            True,
+        ),  # Success - weights set no wait
+        (
+            True,
+            False,
+            [1, 2],
+            [2000, 20],
+            True,
+            True,
+            True,
+        ),  # Success - large value to be normalized
+        (
+            True,
+            False,
+            [1, 2],
+            [2000, 0],
+            True,
+            True,
+            True,
+        ),  # Success - single large value
+        (
+            True,
+            False,
+            [1, 2],
+            [0.5, 0.5],
+            True,
+            False,
+            False,
+        ),  # Failure - prompt declined
+        (
+            True,
+            False,
+            [1, 2],
+            [0.5, 0.5],
+            False,
+            None,
+            False,
+        ),  # Failure - setting weights failed
+        (
+            True,
+            False,
+            [],
+            [],
+            None,
+            False,
+            False,
+        ),  # Exception catched - ValueError 'min() arg is an empty sequence'
+    ],
+    ids=[
+        "success-weights-set",
+        "success-not-wait",
+        "success-large-value",
+        "success-single-value",
+        "failure-user-declines",
+        "failure-setting-weights",
+        "failure-value-error-exception",
+    ],
+)
+def test_set_root_weights_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    wait_for_inclusion,
+    wait_for_finalization,
+    netuids,
+    weights,
+    prompt,
+    user_response,
+    expected_success,
+):
+    # Arrange
+    with patch.object(
+        mock_subtensor, "_do_set_weights", return_value=(expected_success, "Mock error")
+    ), patch.object(
+        mock_subtensor, "min_allowed_weights", return_value=0
+    ), patch.object(
+        mock_subtensor, "max_weight_limit", return_value=1
+    ), patch(
+        "rich.prompt.Confirm.ask", return_value=user_response
+    ) as mock_confirm:
+        # Act
+        result = set_root_weights_extrinsic(
+            subtensor=mock_subtensor,
+            wallet=mock_wallet,
+            netuids=netuids,
+            weights=weights,
+            version_key=0,
+            wait_for_inclusion=wait_for_inclusion,
+            wait_for_finalization=wait_for_finalization,
+            prompt=prompt,
+        )
+
+        # Assert
+        assert result == expected_success
+        if prompt:
+            mock_confirm.assert_called_once()
+        else:
+            mock_confirm.assert_not_called()
+
+
+@pytest.mark.parametrize(
+    "wait_for_inclusion, wait_for_finalization, netuids, weights, prompt, user_response, expected_success",
+    [
+        (True, False, [1, 2], [0.5, 0.5], True, True, True),  # Success - weights set
+        (
+            False,
+            False,
+            [1, 2],
+            [0.5, 0.5],
+            False,
+            None,
+            True,
+        ),  # Success - weights set no wait
+        (
+            True,
+            False,
+            [1, 2],
+            [2000, 20],
+            True,
+            True,
+            True,
+        ),  # Success - large value to be normalized
+        (
+            True,
+            False,
+            [1, 2],
+            [2000, 0],
+            True,
+            True,
+            True,
+        ),  # Success - single large value
+        (
+            True,
+            False,
+            [1, 2],
+            [0.5, 0.5],
+            True,
+            False,
+            False,
+        ),  # Failure - prompt declined
+        (
+            True,
+            False,
+            [1, 2],
+            [0.5, 0.5],
+            False,
+            None,
+            False,
+        ),  # Failure - setting weights failed
+        (
+            True,
+            False,
+            [],
+            [],
+            None,
+            False,
+            False,
+        ),  # Exception catched - ValueError 'min() arg is an empty sequence'
+    ],
+    ids=[
+        "success-weights-set",
+        "success-not-wait",
+        "success-large-value",
+        "success-single-value",
+        "failure-user-declines",
+        "failure-setting-weights",
+        "failure-value-error-exception",
+    ],
+)
+def test_set_root_weights_extrinsic_torch(
+    mock_subtensor,
+    mock_wallet,
+    wait_for_inclusion,
+    wait_for_finalization,
+    netuids,
+    weights,
+    prompt,
+    user_response,
+    expected_success,
+    force_legacy_torch_compat_api,
+):
+    test_set_root_weights_extrinsic(
+        mock_subtensor,
+        mock_wallet,
+        wait_for_inclusion,
+        wait_for_finalization,
+        netuids,
+        weights,
+        prompt,
+        user_response,
+        expected_success,
+    )
diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py
new file mode 100644
index 0000000000..68ce7acae9
--- /dev/null
+++ b/tests/unit_tests/extrinsics/test_set_weights.py
@@ -0,0 +1,102 @@
+import torch
+import pytest
+from unittest.mock import MagicMock, patch
+from bittensor import subtensor, wallet
+from bittensor.extrinsics.set_weights import set_weights_extrinsic
+
+
+@pytest.fixture
+def mock_subtensor():
+    mock = MagicMock(spec=subtensor)
+    mock.network = "mock_network"
+    return mock
+
+
+@pytest.fixture
+def mock_wallet():
+    mock = MagicMock(spec=wallet)
+    return mock
+
+
+@pytest.mark.parametrize(
+    "uids, weights, version_key, wait_for_inclusion, wait_for_finalization, prompt, user_accepts, expected_success, expected_message",
+    [
+        (
+            [1, 2],
+            [0.5, 0.5],
+            0,
+            True,
+            False,
+            True,
+            True,
+            True,
+            "Successfully set weights and Finalized.",
+        ),
+        (
+            [1, 2],
+            [0.5, 0.4],
+            0,
+            False,
+            False,
+            False,
+            True,
+            True,
+            "Not waiting for finalization or inclusion.",
+        ),
+        ([1, 2], [0.5, 0.5], 0, True, False, True, True, False, "Mock error message"),
+        ([1, 2], [0.5, 0.5], 0, True, True, True, False, False, "Prompt refused."),
+    ],
+    ids=[
+        "happy-flow",
+        "not-waiting-finalization-inclusion",
+        "error-flow",
+        "prompt-refused",
+    ],
+)
+def test_set_weights_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    uids,
+    weights,
+    version_key,
+    wait_for_inclusion,
+    wait_for_finalization,
+    prompt,
+    user_accepts,
+    expected_success,
+    expected_message,
+):
+    uids_tensor = torch.tensor(uids, dtype=torch.int64)
+    weights_tensor = torch.tensor(weights, dtype=torch.float32)
+    with patch(
+        "bittensor.utils.weight_utils.convert_weights_and_uids_for_emit",
+        return_value=(uids_tensor, weights_tensor),
+    ), patch("rich.prompt.Confirm.ask", return_value=user_accepts), patch.object(
+        mock_subtensor,
+        "_do_set_weights",
+        return_value=(expected_success, "Mock error message"),
+    ) as mock_do_set_weights:
+        result, message = set_weights_extrinsic(
+            subtensor=mock_subtensor,
+            wallet=mock_wallet,
+            netuid=123,
+            uids=uids,
+            weights=weights,
+            version_key=version_key,
+            wait_for_inclusion=wait_for_inclusion,
+            wait_for_finalization=wait_for_finalization,
+            prompt=prompt,
+        )
+
+        assert result == expected_success, f"Test {expected_message} failed."
+        assert message == expected_message, f"Test {expected_message} failed."
+        if user_accepts is not False:
+            mock_do_set_weights.assert_called_once_with(
+                wallet=mock_wallet,
+                netuid=123,
+                uids=uids_tensor,
+                vals=weights_tensor,
+                version_key=version_key,
+                wait_for_finalization=wait_for_finalization,
+                wait_for_inclusion=wait_for_inclusion,
+            )
diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py
new file mode 100644
index 0000000000..0a77ceb2c7
--- /dev/null
+++ b/tests/unit_tests/extrinsics/test_staking.py
@@ -0,0 +1,545 @@
+import pytest
+from unittest.mock import patch, MagicMock
+import bittensor
+from bittensor.utils.balance import Balance
+from bittensor.extrinsics.staking import (
+    add_stake_extrinsic,
+    add_stake_multiple_extrinsic,
+)
+from bittensor.errors import NotDelegateError
+
+
+# Mocking external dependencies
+@pytest.fixture
+def mock_subtensor():
+    mock = MagicMock(spec=bittensor.subtensor)
+    mock.network = "mock_network"
+    return mock
+
+
+@pytest.fixture
+def mock_wallet():
+    mock = MagicMock(spec=bittensor.wallet)
+    mock.hotkey.ss58_address = "5FHneW46..."
+    mock.coldkeypub.ss58_address = "5Gv8YYFu8..."
+    mock.hotkey_str = "mock_hotkey_str"
+    mock.name = "mock_wallet"
+    return mock
+
+
+@pytest.fixture
+def mock_other_owner_wallet():
+    mock = MagicMock(spec=bittensor.wallet)
+    mock.hotkey.ss58_address = "11HneC46..."
+    mock.coldkeypub.ss58_address = "6Gv9ZZFu8..."
+    mock.hotkey_str = "mock_hotkey_str_other_owner"
+    mock.name = "mock_wallet_other_owner"
+    return mock
+
+
+# Parametrized test cases
+@pytest.mark.parametrize(
+    "hotkey_ss58, hotkey_owner, hotkey_delegate, amount, wait_for_inclusion, wait_for_finalization, prompt, user_accepts, expected_success, exception",
+    [
+        # Simple staking to own hotkey, float
+        (None, True, None, 10.0, True, False, False, None, True, None),
+        # Simple staking to own hotkey, int
+        (None, True, None, 10, True, False, False, None, True, None),
+        # Not waiting for inclusion & finalization, own hotkey
+        ("5FHneW46...", True, None, 10.0, False, False, False, None, True, None),
+        # Prompt refused
+        (None, True, None, 10.0, True, False, True, False, False, None),
+        # Stake all
+        (None, True, None, None, True, False, False, None, True, None),
+        # Insufficient balance
+        (None, True, None, 110, True, False, False, None, False, None),
+        # No deduction scenario
+        (None, True, None, 0.000000100, True, False, False, None, True, None),
+        # Not owner but Delegate
+        ("5FHneW46...", False, True, 10.0, True, False, False, None, True, None),
+        # Not owner but Delegate and prompt refused
+        ("5FHneW46...", False, True, 10.0, True, False, True, False, False, None),
+        # Not owner and not delegate
+        (
+            "5FHneW46...",
+            False,
+            False,
+            10.0,
+            True,
+            False,
+            False,
+            None,
+            False,
+            NotDelegateError,
+        ),
+        # Staking failed
+        (None, True, None, 10.0, True, False, False, None, False, None),
+    ],
+    ids=[
+        "success-own-hotkey-float",
+        "success-own-hotkey-int",
+        "success-own-hotkey-no-wait",
+        "prompt-refused",
+        "success-staking-all",
+        "failure-insufficient-balance",
+        "success-no-deduction",
+        "success-delegate",
+        "failure-delegate-prompt-refused",
+        "failure-not-delegate",
+        "failure-staking",
+    ],
+)
+def test_add_stake_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    mock_other_owner_wallet,
+    hotkey_ss58,
+    hotkey_owner,
+    hotkey_delegate,
+    amount,
+    wait_for_inclusion,
+    wait_for_finalization,
+    prompt,
+    user_accepts,
+    expected_success,
+    exception,
+):
+    # Arrange
+    if not amount:
+        staking_balance = amount if amount else Balance.from_tao(100)
+    else:
+        staking_balance = (
+            Balance.from_tao(amount)
+            if not isinstance(amount, bittensor.Balance)
+            else amount
+        )
+
+    if staking_balance > bittensor.Balance.from_rao(1000):
+        staking_balance = staking_balance - bittensor.Balance.from_rao(1000)
+
+    with patch.object(
+        mock_subtensor, "_do_stake", return_value=expected_success
+    ) as mock_add_stake, patch.object(
+        mock_subtensor, "get_balance", return_value=Balance.from_tao(100)
+    ), patch.object(
+        mock_subtensor,
+        "get_stake_for_coldkey_and_hotkey",
+        return_value=Balance.from_tao(50),
+    ), patch.object(
+        mock_subtensor,
+        "get_hotkey_owner",
+        return_value=mock_wallet.coldkeypub.ss58_address
+        if hotkey_owner
+        else mock_other_owner_wallet.coldkeypub.ss58_address,
+    ), patch.object(
+        mock_subtensor, "is_hotkey_delegate", return_value=hotkey_delegate
+    ), patch.object(
+        mock_subtensor, "get_delegate_take", return_value=0.01
+    ), patch(
+        "rich.prompt.Confirm.ask", return_value=user_accepts
+    ) as mock_confirm:
+        # Act
+        if not hotkey_owner and not hotkey_delegate:
+            with pytest.raises(exception):
+                result = add_stake_extrinsic(
+                    subtensor=mock_subtensor,
+                    wallet=mock_wallet,
+                    hotkey_ss58=hotkey_ss58,
+                    amount=amount,
+                    wait_for_inclusion=wait_for_inclusion,
+                    wait_for_finalization=wait_for_finalization,
+                    prompt=prompt,
+                )
+        else:
+            result = add_stake_extrinsic(
+                subtensor=mock_subtensor,
+                wallet=mock_wallet,
+                hotkey_ss58=hotkey_ss58,
+                amount=amount,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+                prompt=prompt,
+            )
+
+            # Assert
+            assert (
+                result == expected_success
+            ), f"Expected {expected_success}, but got {result}"
+
+            if prompt:
+                mock_confirm.assert_called_once()
+
+            if expected_success:
+                if not hotkey_ss58:
+                    hotkey_ss58 = mock_wallet.hotkey.ss58_address
+
+                mock_add_stake.assert_called_once_with(
+                    wallet=mock_wallet,
+                    hotkey_ss58=hotkey_ss58,
+                    amount=staking_balance,
+                    wait_for_inclusion=wait_for_inclusion,
+                    wait_for_finalization=wait_for_finalization,
+                )
+
+
+# Parametrized test cases
+@pytest.mark.parametrize(
+    "hotkey_ss58s, amounts, hotkey_owner, hotkey_delegates ,wallet_balance, wait_for_inclusion, wait_for_finalization, prompt, prompt_response, stake_responses, expected_success, stake_attempted, exception, exception_msg",
+    [
+        # Successful stake
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [True, True],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Successful stake with prompt
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            True,
+            True,
+            True,
+            [True, True],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Successful stake, no deduction scenario
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [0.000000100, 0.000000100],
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [True, True],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Successful stake, not waiting for finalization & inclusion
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, True],
+            [None, None],
+            100.0,
+            False,
+            False,
+            False,
+            None,
+            [True, True],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Successful stake, one key is a delegate
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, False],
+            [True, True],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [True, True],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Partial successful stake, one key is not a delegate
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, False],
+            [True, False],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [True, False],
+            True,
+            1,
+            None,
+            None,
+        ),
+        # Successful, staking all tao to first wallet, not waiting for finalization + inclusion
+        (
+            ["5FHneW46...", "11HneC46..."],
+            None,
+            [True, True],
+            [None, None],
+            100.0,
+            False,
+            False,
+            False,
+            None,
+            [True, False],
+            True,
+            1,
+            None,
+            None,
+        ),
+        # Successful, staking all tao to first wallet
+        (
+            ["5FHneW46...", "11HneC46..."],
+            None,
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [True, False],
+            True,
+            1,
+            None,
+            None,
+        ),
+        # Success, staking 0 tao
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [0.0, 0.0],
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            True,
+            0,
+            None,
+            None,
+        ),
+        # Complete failure to stake for both keys
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [False, False],
+            False,
+            2,
+            None,
+            None,
+        ),
+        # Complete failure, both keys are not delegates
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [False, False],
+            [False, False],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [False, False],
+            False,
+            0,
+            None,
+            None,
+        ),
+        # Unsuccessful stake with prompt declined both times
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0, 20.0],
+            [True, True],
+            [None, None],
+            100.0,
+            True,
+            True,
+            True,
+            False,
+            [None, None],
+            False,
+            0,
+            None,
+            None,
+        ),
+        # Exception, TypeError for incorrect hotkey_ss58s
+        (
+            [123, "11HneC46..."],
+            [10.0, 20.0],
+            [False, False],
+            [False, False],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            None,
+            0,
+            TypeError,
+            "hotkey_ss58s must be a list of str",
+        ),
+        # Exception, ValueError for mismatch between hotkeys and amounts
+        (
+            ["5FHneW46...", "11HneC46..."],
+            [10.0],
+            [False, False],
+            [False, False],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            None,
+            0,
+            ValueError,
+            "amounts must be a list of the same length as hotkey_ss58s",
+        ),
+        # Exception, TypeError for incorrect amounts
+        (
+            ["5FHneW46...", "11HneC46..."],
+            ["abc", 12],
+            [False, False],
+            [False, False],
+            100.0,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            None,
+            0,
+            TypeError,
+            "amounts must be a [list of bittensor.Balance or float] or None",
+        ),
+    ],
+    ids=[
+        "success-basic-path",
+        "success-with-prompt",
+        "success-no-deduction",
+        "success-no-wait",
+        "success-one-delegate",
+        "partial-success-one-not-delegate",
+        "success-all-tao-no-wait",
+        "success-all-tao",
+        "success-0-tao",
+        "failure-both-keys",
+        "failure-both-not-delegates",
+        "failure-prompt-declined",
+        "failure-type-error-hotkeys",
+        "failure-value-error-amount",
+        "failure-type-error-amount",
+    ],
+)
+def test_add_stake_multiple_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    mock_other_owner_wallet,
+    hotkey_ss58s,
+    amounts,
+    hotkey_owner,
+    hotkey_delegates,
+    wallet_balance,
+    wait_for_inclusion,
+    wait_for_finalization,
+    prompt,
+    prompt_response,
+    stake_responses,
+    expected_success,
+    stake_attempted,
+    exception,
+    exception_msg,
+):
+    # Arrange
+    def hotkey_delegate_side_effect(hotkey_ss58):
+        index = hotkey_ss58s.index(hotkey_ss58)
+        return hotkey_delegates[index]
+
+    def owner_side_effect(hotkey_ss58):
+        index = hotkey_ss58s.index(hotkey_ss58)
+        return (
+            mock_wallet.coldkeypub.ss58_address
+            if hotkey_owner[index]
+            else mock_other_owner_wallet.coldkeypub.ss58_address
+        )
+
+    def stake_side_effect(hotkey_ss58, *args, **kwargs):
+        index = hotkey_ss58s.index(hotkey_ss58)
+        return stake_responses[index]
+
+    with patch.object(
+        mock_subtensor, "get_balance", return_value=Balance.from_tao(wallet_balance)
+    ), patch.object(
+        mock_subtensor, "is_hotkey_delegate", side_effect=hotkey_delegate_side_effect
+    ), patch.object(
+        mock_subtensor, "get_hotkey_owner", side_effect=owner_side_effect
+    ), patch.object(
+        mock_subtensor, "_do_stake", side_effect=stake_side_effect
+    ) as mock_do_stake, patch.object(
+        mock_subtensor, "tx_rate_limit", return_value=0
+    ), patch(
+        "rich.prompt.Confirm.ask", return_value=prompt_response
+    ) as mock_confirm:
+        # Act
+        if exception:
+            with pytest.raises(exception) as exc_info:
+                result = add_stake_multiple_extrinsic(
+                    subtensor=mock_subtensor,
+                    wallet=mock_wallet,
+                    hotkey_ss58s=hotkey_ss58s,
+                    amounts=amounts,
+                    wait_for_inclusion=wait_for_inclusion,
+                    wait_for_finalization=wait_for_finalization,
+                    prompt=prompt,
+                )
+            # Assert
+            assert str(exc_info.value) == exception_msg
+
+        # Act
+        else:
+            result = add_stake_multiple_extrinsic(
+                subtensor=mock_subtensor,
+                wallet=mock_wallet,
+                hotkey_ss58s=hotkey_ss58s,
+                amounts=amounts,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+                prompt=prompt,
+            )
+
+            # Assert
+            assert (
+                result == expected_success
+            ), f"Expected {expected_success}, but got {result}"
+            if prompt:
+                assert mock_confirm.called
+            assert mock_do_stake.call_count == stake_attempted
diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py
new file mode 100644
index 0000000000..d6ff094dd8
--- /dev/null
+++ b/tests/unit_tests/extrinsics/test_unstaking.py
@@ -0,0 +1,336 @@
+import bittensor
+import pytest
+
+from unittest.mock import patch, MagicMock
+
+from bittensor.utils.balance import Balance
+from bittensor.extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic
+
+
+@pytest.fixture
+def mock_subtensor():
+    mock = MagicMock(spec=bittensor.subtensor)
+    mock.network = "mock_network"
+    return mock
+
+
+@pytest.fixture
+def mock_wallet():
+    mock = MagicMock(spec=bittensor.wallet)
+    mock.hotkey.ss58_address = "5FHneW46..."
+    mock.coldkeypub.ss58_address = "5Gv8YYFu8..."
+    mock.hotkey_str = "mock_hotkey_str"
+    return mock
+
+
+def mock_get_minimum_required_stake():
+    # Valid minimum threshold as of 2024/05/01
+    return Balance.from_rao(100_000_000)
+
+
+@pytest.mark.parametrize(
+    "hotkey_ss58, amount, wait_for_inclusion, wait_for_finalization, prompt, user_accepts, expected_success, unstake_attempted",
+    [
+        # Successful unstake without waiting for inclusion or finalization
+        (None, 10.0, False, False, False, None, True, True),
+        # Successful unstake with prompt accepted
+        ("5FHneW46...", 10.0, True, True, True, True, True, True),
+        # Prompt declined
+        ("5FHneW46...", 10.0, True, True, True, False, False, False),
+        # Not enough stake to unstake
+        ("5FHneW46...", 1000.0, True, True, False, None, False, False),
+        # Unsuccessful - unstake threshold not reached
+        (None, 0.01, True, True, False, None, False, False),
+        # Successful unstaking all
+        (None, None, False, False, False, None, True, True),
+        # Failure - unstaking failed
+        (None, 10.0, False, False, False, None, False, True),
+    ],
+    ids=[
+        "successful-no-wait",
+        "successful-with-prompt",
+        "failure-prompt-declined",
+        "failure-not-enough-stake",
+        "failure-threshold-not-reached",
+        "success-unstake-all",
+        "failure-unstake-failed",
+    ],
+)
+def test_unstake_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    hotkey_ss58,
+    amount,
+    wait_for_inclusion,
+    wait_for_finalization,
+    prompt,
+    user_accepts,
+    expected_success,
+    unstake_attempted,
+):
+    mock_current_stake = Balance.from_tao(50)
+    mock_current_balance = Balance.from_tao(100)
+
+    with patch.object(
+        mock_subtensor, "_do_unstake", return_value=(expected_success)
+    ), patch.object(
+        mock_subtensor, "get_balance", return_value=mock_current_balance
+    ), patch.object(
+        mock_subtensor,
+        "get_minimum_required_stake",
+        side_effect=mock_get_minimum_required_stake,
+    ), patch.object(
+        mock_subtensor,
+        "get_stake_for_coldkey_and_hotkey",
+        return_value=mock_current_stake,
+    ), patch(
+        "rich.prompt.Confirm.ask", return_value=user_accepts
+    ) as mock_confirm:
+        result = unstake_extrinsic(
+            subtensor=mock_subtensor,
+            wallet=mock_wallet,
+            hotkey_ss58=hotkey_ss58,
+            amount=amount,
+            wait_for_inclusion=wait_for_inclusion,
+            wait_for_finalization=wait_for_finalization,
+            prompt=prompt,
+        )
+
+        assert (
+            result == expected_success
+        ), f"Expected result {expected_success}, but got {result}"
+
+        if prompt:
+            mock_confirm.assert_called_once()
+
+        if unstake_attempted:
+            mock_subtensor._do_unstake.assert_called_once_with(
+                wallet=mock_wallet,
+                hotkey_ss58=hotkey_ss58 or mock_wallet.hotkey.ss58_address,
+                amount=bittensor.Balance.from_tao(amount)
+                if amount
+                else mock_current_stake,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+            )
+        else:
+            mock_subtensor._do_unstake.assert_not_called()
+
+
+@pytest.mark.parametrize(
+    # TODO: Write dynamic test to test for amount = None with multiple hotkeys
+    "hotkey_ss58s, amounts, wallet_balance, wait_for_inclusion, wait_for_finalization, prompt, prompt_response, unstake_responses, expected_success, unstake_attempted, exception, exception_msg",
+    [
+        # Successful unstake - no wait
+        (
+            ["5FHneW46...", "5FHneW47..."],
+            [10.0, 20.0],
+            100,
+            False,
+            False,
+            True,
+            True,
+            [True, True],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Partial-success unstake - one unstake fails
+        (
+            ["5FHneW46...", "5FHneW47..."],
+            [10.0, 20.0],
+            100,
+            True,
+            False,
+            True,
+            True,
+            [True, False],
+            True,
+            2,
+            None,
+            None,
+        ),
+        # Success, based on no hotkeys - func to be confirmed
+        ([], [], 100, True, True, False, None, [None], True, 0, None, None),
+        # Unsuccessful unstake - not enough stake
+        (
+            ["5FHneW46..."],
+            [1000.0],
+            100,
+            True,
+            True,
+            False,
+            True,
+            [None],
+            False,
+            0,
+            None,
+            None,
+        ),
+        # Unsuccessful unstake - threshold not reached
+        (
+            ["5FHneW46..."],
+            [0.01],
+            100,
+            True,
+            True,
+            False,
+            True,
+            [None],
+            False,
+            0,
+            None,
+            None,
+        ),
+        # Unsuccessful unstake with prompt declined both times
+        (
+            ["5FHneW46...", "5FHneW48..."],
+            [10.0, 10.0],
+            100,
+            True,
+            True,
+            True,
+            False,
+            [None, None],
+            False,
+            0,
+            None,
+            None,
+        ),
+        # Exception, TypeError for incorrect hotkey_ss58s
+        (
+            ["5FHneW46...", 123],
+            [10.0, 20.0],
+            100,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            None,
+            0,
+            TypeError,
+            "hotkey_ss58s must be a list of str",
+        ),
+        # Exception, ValueError for mismatch between hotkeys and amounts
+        (
+            ["5FHneW46...", "5FHneW48..."],
+            [10.0],
+            100,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            None,
+            0,
+            ValueError,
+            "amounts must be a list of the same length as hotkey_ss58s",
+        ),
+        # Exception, TypeError for incorrect amounts
+        (
+            ["5FHneW46...", "5FHneW48..."],
+            [10.0, "tao"],
+            100,
+            True,
+            False,
+            False,
+            None,
+            [None, None],
+            None,
+            0,
+            TypeError,
+            "amounts must be a [list of bittensor.Balance or float] or None",
+        ),
+    ],
+    ids=[
+        "success-no-wait",
+        "partial-success-one-fail",
+        "success-no-hotkey",
+        "failure-not-enough-stake",
+        "failure-threshold-not-reached",
+        "failure-prompt-declined",
+        "failure-type-error-hotkeys",
+        "failure-value-error-amounts",
+        "failure-type-error-amounts",
+    ],
+)
+def test_unstake_multiple_extrinsic(
+    mock_subtensor,
+    mock_wallet,
+    hotkey_ss58s,
+    amounts,
+    wallet_balance,
+    wait_for_inclusion,
+    wait_for_finalization,
+    prompt,
+    prompt_response,
+    unstake_responses,
+    expected_success,
+    unstake_attempted,
+    exception,
+    exception_msg,
+):
+    # Arrange
+    mock_current_stake = Balance.from_tao(100)
+    amounts_in_balances = [
+        Balance.from_tao(amount) if isinstance(amount, float) else amount
+        for amount in amounts
+    ]
+
+    def unstake_side_effect(hotkey_ss58, *args, **kwargs):
+        index = hotkey_ss58s.index(hotkey_ss58)
+        return unstake_responses[index]
+
+    with patch.object(
+        mock_subtensor, "_do_unstake", side_effect=unstake_side_effect
+    ) as mock_unstake, patch.object(
+        mock_subtensor,
+        "get_minimum_required_stake",
+        side_effect=mock_get_minimum_required_stake,
+    ), patch.object(
+        mock_subtensor, "get_balance", return_value=Balance.from_tao(wallet_balance)
+    ), patch.object(
+        mock_subtensor, "tx_rate_limit", return_value=0
+    ), patch.object(
+        mock_subtensor,
+        "get_stake_for_coldkey_and_hotkey",
+        return_value=mock_current_stake,
+    ), patch(
+        "rich.prompt.Confirm.ask", return_value=prompt_response
+    ) as mock_confirm:
+        # Act
+        if exception:
+            with pytest.raises(exception) as exc_info:
+                result = unstake_multiple_extrinsic(
+                    subtensor=mock_subtensor,
+                    wallet=mock_wallet,
+                    hotkey_ss58s=hotkey_ss58s,
+                    amounts=amounts,
+                    wait_for_inclusion=wait_for_inclusion,
+                    wait_for_finalization=wait_for_finalization,
+                    prompt=prompt,
+                )
+            # Assert
+            assert str(exc_info.value) == exception_msg
+
+        # Act
+        else:
+            result = unstake_multiple_extrinsic(
+                subtensor=mock_subtensor,
+                wallet=mock_wallet,
+                hotkey_ss58s=hotkey_ss58s,
+                amounts=amounts_in_balances,
+                wait_for_inclusion=wait_for_inclusion,
+                wait_for_finalization=wait_for_finalization,
+                prompt=prompt,
+            )
+
+            # Assert
+            assert (
+                result == expected_success
+            ), f"Expected {expected_success}, but got {result}"
+            if prompt:
+                assert mock_confirm.called
+            assert mock_unstake.call_count == unstake_attempted
diff --git a/tests/unit_tests/factories/__init__.py b/tests/unit_tests/factories/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/unit_tests/factories/neuron_factory.py b/tests/unit_tests/factories/neuron_factory.py
new file mode 100644
index 0000000000..4ad70c5dca
--- /dev/null
+++ b/tests/unit_tests/factories/neuron_factory.py
@@ -0,0 +1,63 @@
+import factory
+
+from bittensor.chain_data import AxonInfo, NeuronInfoLite, PrometheusInfo
+from bittensor.utils.balance import Balance
+
+
+class BalanceFactory(factory.Factory):
+    class Meta:
+        model = Balance
+
+    balance = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+
+
+class PrometheusInfoFactory(factory.Factory):
+    class Meta:
+        model = PrometheusInfo
+
+    block = factory.Faker("random_int", min=0, max=100)
+    version = factory.Faker("random_int", min=0, max=100)
+    ip = factory.Faker("ipv4")
+    port = factory.Faker("random_int", min=0, max=100)
+    ip_type = factory.Faker("random_int", min=0, max=100)
+
+
+class AxonInfoFactory(factory.Factory):
+    class Meta:
+        model = AxonInfo
+
+    version = factory.Faker("random_int", min=0, max=100)
+    ip = factory.Faker("ipv4")
+    port = factory.Faker("random_int", min=0, max=100)
+    ip_type = factory.Faker("random_int", min=0, max=100)
+    hotkey = factory.Faker("uuid4")
+    coldkey = factory.Faker("uuid4")
+
+
+class NeuronInfoLiteFactory(factory.Factory):
+    class Meta:
+        model = NeuronInfoLite
+
+    hotkey = factory.Faker("uuid4")
+    coldkey = factory.Faker("uuid4")
+    uid = factory.Sequence(lambda n: n)
+    netuid = factory.Sequence(lambda n: n)
+    active = factory.Faker("random_int", min=0, max=1)
+    stake = factory.SubFactory(BalanceFactory)
+    stake_dict = factory.Dict({"balance": 10})
+    total_stake = factory.SubFactory(BalanceFactory)
+    rank = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+    emission = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+    incentive = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+    consensus = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+    trust = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+    validator_trust = factory.Faker(
+        "pyfloat", left_digits=3, right_digits=6, positive=True
+    )
+    dividends = factory.Faker("pyfloat", left_digits=3, right_digits=6, positive=True)
+    last_update = factory.Faker("unix_time")
+    validator_permit = factory.Faker("boolean")
+    prometheus_info = factory.SubFactory(PrometheusInfoFactory)
+    axon_info = factory.SubFactory(AxonInfoFactory)
+    pruning_score = factory.Faker("random_int", min=0, max=100)
+    is_null = factory.Faker("boolean")
diff --git a/tests/unit_tests/test_chain_data.py b/tests/unit_tests/test_chain_data.py
index d8e1c1eb0c..a6474bbee9 100644
--- a/tests/unit_tests/test_chain_data.py
+++ b/tests/unit_tests/test_chain_data.py
@@ -1,6 +1,6 @@
 import pytest
-import torch
 import bittensor
+import torch
 from bittensor.chain_data import AxonInfo, ChainDataType, DelegateInfo, NeuronInfo
 
 SS58_FORMAT = bittensor.__ss58_format__
@@ -198,6 +198,36 @@ def test_to_parameter_dict(axon_info, test_case):
     # Act
     result = axon_info.to_parameter_dict()
 
+    # Assert
+    assert isinstance(result, dict)
+    for key, value in axon_info.__dict__.items():
+        assert key in result
+        assert result[key] == value, f"Test case: {test_case}"
+
+
+@pytest.mark.parametrize(
+    "axon_info, test_case",
+    [
+        (
+            AxonInfo(
+                version=1,
+                ip="127.0.0.1",
+                port=8080,
+                ip_type=4,
+                hotkey="hot",
+                coldkey="cold",
+            ),
+            "ID_to_parameter_dict",
+        ),
+    ],
+)
+def test_to_parameter_dict_torch(
+    axon_info,
+    test_case,
+    force_legacy_torch_compat_api,
+):
+    result = axon_info.to_parameter_dict()
+
     # Assert
     assert isinstance(result, torch.nn.ParameterDict)
     for key, value in axon_info.__dict__.items():
@@ -205,6 +235,38 @@ def test_to_parameter_dict(axon_info, test_case):
         assert result[key] == value, f"Test case: {test_case}"
 
 
+@pytest.mark.parametrize(
+    "parameter_dict, expected, test_case",
+    [
+        (
+            {
+                "version": 1,
+                "ip": "127.0.0.1",
+                "port": 8080,
+                "ip_type": 4,
+                "hotkey": "hot",
+                "coldkey": "cold",
+            },
+            AxonInfo(
+                version=1,
+                ip="127.0.0.1",
+                port=8080,
+                ip_type=4,
+                hotkey="hot",
+                coldkey="cold",
+            ),
+            "ID_from_parameter_dict",
+        ),
+    ],
+)
+def test_from_parameter_dict(parameter_dict, expected, test_case):
+    # Act
+    result = AxonInfo.from_parameter_dict(parameter_dict)
+
+    # Assert
+    assert result == expected, f"Test case: {test_case}"
+
+
 @pytest.mark.parametrize(
     "parameter_dict, expected, test_case",
     [
@@ -231,7 +293,9 @@ def test_to_parameter_dict(axon_info, test_case):
         ),
     ],
 )
-def test_from_parameter_dict(parameter_dict, expected, test_case):
+def test_from_parameter_dict_torch(
+    parameter_dict, expected, test_case, force_legacy_torch_compat_api
+):
     # Act
     result = AxonInfo.from_parameter_dict(parameter_dict)
 
diff --git a/tests/unit_tests/test_dendrite.py b/tests/unit_tests/test_dendrite.py
index 09219816e8..9b0b6d7ddf 100644
--- a/tests/unit_tests/test_dendrite.py
+++ b/tests/unit_tests/test_dendrite.py
@@ -21,7 +21,7 @@
 import pytest
 import typing
 import bittensor
-from unittest.mock import MagicMock, Mock, patch
+from unittest.mock import MagicMock, Mock
 from tests.helpers import _get_mock_wallet
 
 from bittensor.synapse import TerminalInfo
@@ -253,9 +253,11 @@ def test_terminal_info_edge_cases(
 @pytest.mark.parametrize(
     "status_code, process_time, port, ip, version, nonce, expected_exception",
     [
-        (None, 0.1, 9282, 111, TerminalInfo(), 111111, ValidationError),
+        (None, 0.1, 9282, 111, TerminalInfo(), 111111, TypeError),
+    ],
+    ids=[
+        "int() argument must be a string, a bytes-like object or a real number, not 'TerminalInfo'"
     ],
-    ids=["missing-required-field"],
 )
 def test_terminal_info_error_cases(
     status_code, process_time, port, ip, version, nonce, expected_exception
diff --git a/tests/unit_tests/test_keyfile.py b/tests/unit_tests/test_keyfile.py
index 266b234062..d20af809f9 100644
--- a/tests/unit_tests/test_keyfile.py
+++ b/tests/unit_tests/test_keyfile.py
@@ -28,6 +28,8 @@
 from substrateinterface.exceptions import ConfigurationError
 from bip39 import bip39_validate
 
+from bittensor import get_coldkey_password_from_environment
+
 
 def test_generate_mnemonic():
     """
@@ -606,3 +608,18 @@ def test_deserialize_keypair_from_keyfile_data(keyfile_setup_teardown):
     assert deserialized_keypair.ss58_address == keypair.ss58_address
     assert deserialized_keypair.public_key == keypair.public_key
     assert deserialized_keypair.private_key == keypair.private_key
+
+
+def test_get_coldkey_password_from_environment(monkeypatch):
+    password_by_wallet = {
+        "WALLET": "password",
+        "my_wallet": "password",
+    }
+
+    monkeypatch.setenv("bt_cold_pw_wallet", password_by_wallet["WALLET"])
+    monkeypatch.setenv("BT_COLD_PW_My_Wallet", password_by_wallet["my_wallet"])
+
+    for wallet, password in password_by_wallet.items():
+        assert get_coldkey_password_from_environment(wallet) == password
+
+    assert get_coldkey_password_from_environment("non_existent_wallet") is None
diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py
index 0c1aac2637..af0dbdba76 100644
--- a/tests/unit_tests/test_metagraph.py
+++ b/tests/unit_tests/test_metagraph.py
@@ -17,12 +17,11 @@
 
 from unittest.mock import Mock
 import pytest
-import torch
+import numpy as np
 import bittensor
 
 from bittensor.metagraph import metagraph as Metagraph
 from unittest.mock import MagicMock
-from loguru import logger
 
 
 @pytest.fixture
@@ -66,27 +65,27 @@ def test_set_metagraph_attributes(mock_environment):
     assert metagraph.n.item() == len(neurons)
     assert metagraph.block.item() == 5
     assert (
-        torch.equal(
+        np.array_equal(
             metagraph.uids,
-            torch.tensor([neuron.uid for neuron in neurons], dtype=torch.int64),
+            np.array([neuron.uid for neuron in neurons], dtype=np.int64),
         )
-        == True
+        is True
     )
 
     assert (
-        torch.equal(
+        np.array_equal(
             metagraph.trust,
-            torch.tensor([neuron.trust for neuron in neurons], dtype=torch.float32),
+            np.array([neuron.trust for neuron in neurons], dtype=np.float32),
         )
-        == True
+        is True
     )
 
     assert (
-        torch.equal(
+        np.array_equal(
             metagraph.consensus,
-            torch.tensor([neuron.consensus for neuron in neurons], dtype=torch.float32),
+            np.array([neuron.consensus for neuron in neurons], dtype=np.float32),
         )
-        == True
+        is True
     )
     # Similarly for other attributes...
 
diff --git a/tests/unit_tests/test_overview.py b/tests/unit_tests/test_overview.py
new file mode 100644
index 0000000000..638ab4df4c
--- /dev/null
+++ b/tests/unit_tests/test_overview.py
@@ -0,0 +1,266 @@
+# Standard Lib
+from copy import deepcopy
+from unittest.mock import MagicMock, patch
+
+# Pytest
+import pytest
+
+# Bittensor
+import bittensor
+from bittensor.commands.overview import OverviewCommand
+from tests.unit_tests.factories.neuron_factory import NeuronInfoLiteFactory
+
+
+@pytest.fixture
+def mock_subtensor():
+    mock = MagicMock()
+    mock.get_balance = MagicMock(return_value=100)
+    return mock
+
+
+def fake_config(**kwargs):
+    config = deepcopy(construct_config())
+    for key, value in kwargs.items():
+        setattr(config, key, value)
+    return config
+
+
+def construct_config():
+    parser = bittensor.cli.__create_parser__()
+    defaults = bittensor.config(parser=parser, args=[])
+    # Parse commands and subcommands
+    for command in bittensor.ALL_COMMANDS:
+        if (
+            command in bittensor.ALL_COMMANDS
+            and "commands" in bittensor.ALL_COMMANDS[command]
+        ):
+            for subcommand in bittensor.ALL_COMMANDS[command]["commands"]:
+                defaults.merge(
+                    bittensor.config(parser=parser, args=[command, subcommand])
+                )
+        else:
+            defaults.merge(bittensor.config(parser=parser, args=[command]))
+
+    defaults.netuid = 1
+    # Always use mock subtensor.
+    defaults.subtensor.network = "finney"
+    # Skip version checking.
+    defaults.no_version_checking = True
+
+    return defaults
+
+
+@pytest.fixture
+def mock_wallet():
+    mock = MagicMock()
+    mock.coldkeypub_file.exists_on_device = MagicMock(return_value=True)
+    mock.coldkeypub_file.is_encrypted = MagicMock(return_value=False)
+    mock.coldkeypub.ss58_address = "fake_address"
+    return mock
+
+
+class MockHotkey:
+    def __init__(self, hotkey_str):
+        self.hotkey_str = hotkey_str
+
+
+class MockCli:
+    def __init__(self, config):
+        self.config = config
+
+
+@pytest.mark.parametrize(
+    "config_all, exists_on_device, is_encrypted, expected_balance, test_id",
+    [
+        (True, True, False, 100, "happy_path_all_wallets"),
+        (False, True, False, 100, "happy_path_single_wallet"),
+        (True, False, False, 0, "edge_case_no_wallets_found"),
+        (True, True, True, 0, "edge_case_encrypted_wallet"),
+    ],
+)
+def test_get_total_balance(
+    mock_subtensor,
+    mock_wallet,
+    config_all,
+    exists_on_device,
+    is_encrypted,
+    expected_balance,
+    test_id,
+):
+    # Arrange
+    cli = MockCli(fake_config(all=config_all))
+    mock_wallet.coldkeypub_file.exists_on_device.return_value = exists_on_device
+    mock_wallet.coldkeypub_file.is_encrypted.return_value = is_encrypted
+
+    with patch(
+        "bittensor.wallet", return_value=mock_wallet
+    ) as mock_wallet_constructor, patch(
+        "bittensor.commands.overview.get_coldkey_wallets_for_path",
+        return_value=[mock_wallet] if config_all else [],
+    ), patch(
+        "bittensor.commands.overview.get_all_wallets_for_path",
+        return_value=[mock_wallet],
+    ), patch(
+        "bittensor.commands.overview.get_hotkey_wallets_for_wallet",
+        return_value=[mock_wallet],
+    ):
+        # Act
+        result_hotkeys, result_balance = OverviewCommand._get_total_balance(
+            0, mock_subtensor, cli
+        )
+
+        # Assert
+        assert result_balance == expected_balance, f"Test ID: {test_id}"
+        assert all(
+            isinstance(hotkey, MagicMock) for hotkey in result_hotkeys
+        ), f"Test ID: {test_id}"
+
+
+@pytest.mark.parametrize(
+    "config, all_hotkeys, expected_result, test_id",
+    [
+        # Happy path tests
+        (
+            {"all_hotkeys": False, "hotkeys": ["abc123", "xyz456"]},
+            [MockHotkey("abc123"), MockHotkey("xyz456"), MockHotkey("mno567")],
+            ["abc123", "xyz456"],
+            "test_happy_path_included",
+        ),
+        (
+            {"all_hotkeys": True, "hotkeys": ["abc123", "xyz456"]},
+            [MockHotkey("abc123"), MockHotkey("xyz456"), MockHotkey("mno567")],
+            ["mno567"],
+            "test_happy_path_excluded",
+        ),
+        # Edge cases
+        (
+            {"all_hotkeys": False, "hotkeys": []},
+            [MockHotkey("abc123"), MockHotkey("xyz456")],
+            [],
+            "test_edge_no_hotkeys_specified",
+        ),
+        (
+            {"all_hotkeys": True, "hotkeys": []},
+            [MockHotkey("abc123"), MockHotkey("xyz456")],
+            ["abc123", "xyz456"],
+            "test_edge_all_hotkeys_excluded",
+        ),
+        (
+            {"all_hotkeys": False, "hotkeys": ["abc123", "xyz456"]},
+            [],
+            [],
+            "test_edge_no_hotkeys_available",
+        ),
+        (
+            {"all_hotkeys": True, "hotkeys": ["abc123", "xyz456"]},
+            [],
+            [],
+            "test_edge_no_hotkeys_available_excluded",
+        ),
+    ],
+)
+def test_get_hotkeys(config, all_hotkeys, expected_result, test_id):
+    # Arrange
+    cli = MockCli(
+        fake_config(
+            hotkeys=config.get("hotkeys"), all_hotkeys=config.get("all_hotkeys")
+        )
+    )
+
+    # Act
+    result = OverviewCommand._get_hotkeys(cli, all_hotkeys)
+
+    # Assert
+    assert [
+        hotkey.hotkey_str for hotkey in result
+    ] == expected_result, f"Failed {test_id}"
+
+
+def test_get_hotkeys_error():
+    # Arrange
+    cli = MockCli(fake_config(hotkeys=["abc123", "xyz456"], all_hotkeys=False))
+    all_hotkeys = None
+
+    # Act
+    with pytest.raises(TypeError):
+        OverviewCommand._get_hotkeys(cli, all_hotkeys)
+
+
+@pytest.fixture
+def neuron_info():
+    return [
+        (1, [NeuronInfoLiteFactory(netuid=1)], None),
+        (2, [NeuronInfoLiteFactory(netuid=2)], None),
+    ]
+
+
+@pytest.fixture
+def neurons_dict():
+    return {
+        "1": [NeuronInfoLiteFactory(netuid=1)],
+        "2": [NeuronInfoLiteFactory(netuid=2)],
+    }
+
+
+@pytest.fixture
+def netuids_list():
+    return [1, 2]
+
+
+# Test cases
+@pytest.mark.parametrize(
+    "test_id, results, expected_neurons, expected_netuids",
+    [
+        # Test ID: 01 - Happy path, all neurons processed correctly
+        (
+            "01",
+            [
+                (1, [NeuronInfoLiteFactory(netuid=1)], None),
+                (2, [NeuronInfoLiteFactory(netuid=2)], None),
+            ],
+            {
+                "1": [NeuronInfoLiteFactory(netuid=1)],
+                "2": [NeuronInfoLiteFactory(netuid=2)],
+            },
+            [1, 2],
+        ),
+        # Test ID: 02 - Error message present, should skip processing for that netuid
+        (
+            "02",
+            [
+                (1, [NeuronInfoLiteFactory(netuid=1)], None),
+                (2, [], "Error fetching data"),
+            ],
+            {"1": [NeuronInfoLiteFactory()]},
+            [1],
+        ),
+        # Test ID: 03 - No neurons found for a netuid, should remove the netuid
+        (
+            "03",
+            [(1, [NeuronInfoLiteFactory()], None), (2, [], None)],
+            {"1": [NeuronInfoLiteFactory()]},
+            [1],
+        ),
+        # Test ID: 04 - Mixed conditions
+        (
+            "04",
+            [
+                (1, [NeuronInfoLiteFactory(netuid=1)], None),
+                (2, [], None),
+            ],
+            {"1": [NeuronInfoLiteFactory()]},
+            [1],
+        ),
+    ],
+)
+def test_process_neuron_results(
+    test_id, results, expected_neurons, expected_netuids, neurons_dict, netuids_list
+):
+    # Act
+    actual_neurons = OverviewCommand._process_neuron_results(
+        results, neurons_dict, netuids_list
+    )
+
+    # Assert
+    assert actual_neurons.keys() == expected_neurons.keys(), f"Failed test {test_id}"
+    assert netuids_list == expected_netuids, f"Failed test {test_id}"
diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py
index 14dfe95b90..b9dee7a110 100644
--- a/tests/unit_tests/test_subtensor.py
+++ b/tests/unit_tests/test_subtensor.py
@@ -25,7 +25,8 @@
 
 # Application
 import bittensor
-from bittensor.subtensor import subtensor as Subtensor
+from bittensor.subtensor import subtensor as Subtensor, _logger
+from bittensor import subtensor_module
 
 
 def test_serve_axon_with_external_ip_set():
@@ -176,7 +177,7 @@ def test_stake_multiple():
         # args, kwargs
         _, kwargs = mock_do_stake.call_args
 
-        assert kwargs["ammount"] == pytest.approx(
+        assert kwargs["amount"] == pytest.approx(
             mock_amount.rao, rel=1e9
         )  # delta of 1.0 TAO
 
@@ -272,3 +273,46 @@ def test_determine_chain_endpoint_and_network(
     # Assert
     assert result_network == expected_network
     assert result_endpoint == expected_endpoint
+
+
+@pytest.fixture
+def substrate():
+    class MockSubstrate:
+        pass
+
+    return MockSubstrate()
+
+
+@pytest.fixture
+def subtensor(substrate):
+    mock.patch.object(
+        subtensor_module,
+        "get_subtensor_errors",
+        return_value={
+            "1": ("ErrorOne", "Description one"),
+            "2": ("ErrorTwo", "Description two"),
+        },
+    ).start()
+    return Subtensor()
+
+
+def test_get_error_info_by_index_known_error(subtensor):
+    name, description = subtensor.get_error_info_by_index(1)
+    assert name == "ErrorOne"
+    assert description == "Description one"
+
+
+@pytest.fixture
+def mock_logger():
+    with mock.patch.object(_logger, "warning") as mock_warning:
+        yield mock_warning
+
+
+def test_get_error_info_by_index_unknown_error(subtensor, mock_logger):
+    fake_index = 999
+    name, description = subtensor.get_error_info_by_index(fake_index)
+    assert name == "Unknown Error"
+    assert description == ""
+    mock_logger.assert_called_once_with(
+        f"Subtensor returned an error with an unknown index: {fake_index}"
+    )
diff --git a/tests/unit_tests/test_synapse.py b/tests/unit_tests/test_synapse.py
index 70dd88db76..be5465facb 100644
--- a/tests/unit_tests/test_synapse.py
+++ b/tests/unit_tests/test_synapse.py
@@ -15,16 +15,17 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 import json
-import torch
 import base64
-import typing
+from typing import List, Optional
+
+import pydantic_core
 import pytest
 import bittensor
 
 
 def test_parse_headers_to_inputs():
     class Test(bittensor.Synapse):
-        key1: typing.List[int]
+        key1: List[int]
 
     # Define a mock headers dictionary to use for testing
     headers = {
@@ -59,7 +60,7 @@ class Test(bittensor.Synapse):
 
 def test_from_headers():
     class Test(bittensor.Synapse):
-        key1: typing.List[int]
+        key1: List[int]
 
     # Define a mock headers dictionary to use for testing
     headers = {
@@ -129,9 +130,15 @@ def test_custom_synapse():
     class Test(bittensor.Synapse):
         a: int  # Carried through because required.
         b: int = None  # Not carried through headers
-        c: typing.Optional[int]  # Not carried through headers
-        d: typing.Optional[typing.List[int]]  # Not carried through headers
-        e: typing.List[int]  # Carried through headers
+        c: Optional[int]  # Required, carried through headers, cannot be None
+        d: Optional[List[int]]  # Required, carried though headers, cannot be None
+        e: List[int]  # Carried through headers
+        f: Optional[
+            int
+        ] = None  # Not Required, Not carried through headers, can be None
+        g: Optional[
+            List[int]
+        ] = None  # Not Required, Not carried though headers, can be None
 
     # Create an instance of the custom Synapse subclass
     synapse = Test(
@@ -149,6 +156,8 @@ class Test(bittensor.Synapse):
     assert synapse.c == 3
     assert synapse.d == [1, 2, 3, 4]
     assert synapse.e == [1, 2, 3, 4]
+    assert synapse.f == None
+    assert synapse.g == None
 
     # Convert the Test instance to a headers dictionary
     headers = synapse.to_headers()
@@ -161,9 +170,11 @@ class Test(bittensor.Synapse):
     next_synapse = synapse.from_headers(synapse.to_headers())
     assert next_synapse.a == 0  # Default value is 0
     assert next_synapse.b == None
-    assert next_synapse.c == None
-    assert next_synapse.d == None
+    assert next_synapse.c == 0  # Default is 0
+    assert next_synapse.d == []  # Default is []
     assert next_synapse.e == []  # Empty list is default for list types
+    assert next_synapse.f == None
+    assert next_synapse.g == None
 
 
 def test_body_hash_override():
@@ -184,15 +195,15 @@ def test_required_fields_override():
 
     # Try to set the required_hash_fields property and expect a TypeError
     with pytest.raises(
-        TypeError,
-        match='"required_hash_fields" has allow_mutation set to False and cannot be assigned',
+        pydantic_core.ValidationError,
+        match="required_hash_fields\n  Field is frozen",
     ):
         synapse_instance.required_hash_fields = []
 
 
 def test_default_instance_fields_dict_consistency():
     synapse_instance = bittensor.Synapse()
-    assert synapse_instance.dict() == {
+    assert synapse_instance.model_dump() == {
         "name": "Synapse",
         "timeout": 12.0,
         "total_size": 0,
diff --git a/tests/unit_tests/test_tensor.py b/tests/unit_tests/test_tensor.py
index c09d1d64e0..9939b397e7 100644
--- a/tests/unit_tests/test_tensor.py
+++ b/tests/unit_tests/test_tensor.py
@@ -15,14 +15,24 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 import pytest
-import torch
+import numpy as np
 import bittensor
 import numpy
+import torch
 
 
 # This is a fixture that creates an example tensor for testing
 @pytest.fixture
 def example_tensor():
+    # Create a tensor from a list using PyTorch
+    data = np.array([1, 2, 3, 4])
+
+    # Serialize the tensor into a Tensor instance and return it
+    return bittensor.tensor(data)
+
+
+@pytest.fixture
+def example_tensor_torch(force_legacy_torch_compat_api):
     # Create a tensor from a list using PyTorch
     data = torch.tensor([1, 2, 3, 4])
 
@@ -34,6 +44,13 @@ def test_deserialize(example_tensor):
     # Deserialize the tensor from the Tensor instance
     tensor = example_tensor.deserialize()
 
+    # Check that the result is a np.array with the correct values
+    assert isinstance(tensor, np.ndarray)
+    assert tensor.tolist() == [1, 2, 3, 4]
+
+
+def test_deserialize_torch(example_tensor_torch, force_legacy_torch_compat_api):
+    tensor = example_tensor_torch.deserialize()
     # Check that the result is a PyTorch tensor with the correct values
     assert isinstance(tensor, torch.Tensor)
     assert tensor.tolist() == [1, 2, 3, 4]
@@ -62,7 +79,7 @@ def test_serialize(example_tensor):
     assert example_tensor.dtype == example_tensor.dtype
     assert example_tensor.shape == example_tensor.shape
 
-    assert isinstance(example_tensor.tensor(), torch.Tensor)
+    assert isinstance(example_tensor.tensor(), np.ndarray)
 
     # Check that the Tensor instance has the correct buffer, dtype, and shape
     assert example_tensor.buffer == example_tensor.buffer
@@ -70,7 +87,48 @@ def test_serialize(example_tensor):
     assert example_tensor.shape == example_tensor.shape
 
 
+def test_serialize_torch(example_tensor_torch, force_legacy_torch_compat_api):
+    # Check that the serialized tensor is an instance of Tensor
+    assert isinstance(example_tensor_torch, bittensor.Tensor)
+
+    # Check that the Tensor instance has the correct buffer, dtype, and shape
+    assert example_tensor_torch.buffer == example_tensor_torch.buffer
+    assert example_tensor_torch.dtype == example_tensor_torch.dtype
+    assert example_tensor_torch.shape == example_tensor_torch.shape
+
+    assert isinstance(example_tensor_torch.tolist(), list)
+
+    # Check that the Tensor instance has the correct buffer, dtype, and shape
+    assert example_tensor_torch.buffer == example_tensor_torch.buffer
+    assert example_tensor_torch.dtype == example_tensor_torch.dtype
+    assert example_tensor_torch.shape == example_tensor_torch.shape
+
+    assert isinstance(example_tensor_torch.numpy(), numpy.ndarray)
+
+    # Check that the Tensor instance has the correct buffer, dtype, and shape
+    assert example_tensor_torch.buffer == example_tensor_torch.buffer
+    assert example_tensor_torch.dtype == example_tensor_torch.dtype
+    assert example_tensor_torch.shape == example_tensor_torch.shape
+
+    assert isinstance(example_tensor_torch.tensor(), torch.Tensor)
+
+    # Check that the Tensor instance has the correct buffer, dtype, and shape
+    assert example_tensor_torch.buffer == example_tensor_torch.buffer
+    assert example_tensor_torch.dtype == example_tensor_torch.dtype
+    assert example_tensor_torch.shape == example_tensor_torch.shape
+
+
 def test_buffer_field():
+    # Create a Tensor instance with a specified buffer, dtype, and shape
+    tensor = bittensor.Tensor(
+        buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3]
+    )
+
+    # Check that the buffer field matches the provided value
+    assert tensor.buffer == "0x321e13edqwds231231231232131"
+
+
+def test_buffer_field_torch(force_legacy_torch_compat_api):
     # Create a Tensor instance with a specified buffer, dtype, and shape
     tensor = bittensor.Tensor(
         buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3]
@@ -83,24 +141,48 @@ def test_buffer_field():
 def test_dtype_field():
     # Create a Tensor instance with a specified buffer, dtype, and shape
     tensor = bittensor.Tensor(
-        buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3]
+        buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3]
     )
 
     # Check that the dtype field matches the provided value
+    assert tensor.dtype == "float32"
+
+
+def test_dtype_field_torch(force_legacy_torch_compat_api):
+    tensor = bittensor.Tensor(
+        buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3]
+    )
     assert tensor.dtype == "torch.float32"
 
 
 def test_shape_field():
     # Create a Tensor instance with a specified buffer, dtype, and shape
     tensor = bittensor.Tensor(
-        buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3]
+        buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3]
     )
 
     # Check that the shape field matches the provided value
     assert tensor.shape == [3, 3]
 
 
+def test_shape_field_torch(force_legacy_torch_compat_api):
+    tensor = bittensor.Tensor(
+        buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3]
+    )
+    assert tensor.shape == [3, 3]
+
+
 def test_serialize_all_types():
+    bittensor.tensor(np.array([1], dtype=np.float16))
+    bittensor.tensor(np.array([1], dtype=np.float32))
+    bittensor.tensor(np.array([1], dtype=np.float64))
+    bittensor.tensor(np.array([1], dtype=np.uint8))
+    bittensor.tensor(np.array([1], dtype=np.int32))
+    bittensor.tensor(np.array([1], dtype=np.int64))
+    bittensor.tensor(np.array([1], dtype=bool))
+
+
+def test_serialize_all_types_torch(force_legacy_torch_compat_api):
     bittensor.tensor(torch.tensor([1], dtype=torch.float16))
     bittensor.tensor(torch.tensor([1], dtype=torch.float32))
     bittensor.tensor(torch.tensor([1], dtype=torch.float64))
@@ -111,6 +193,30 @@ def test_serialize_all_types():
 
 
 def test_serialize_all_types_equality():
+    rng = np.random.default_rng()
+
+    tensor = rng.standard_normal((100,), dtype=np.float32)
+    assert np.all(bittensor.tensor(tensor).tensor() == tensor)
+
+    tensor = rng.standard_normal((100,), dtype=np.float64)
+    assert np.all(bittensor.tensor(tensor).tensor() == tensor)
+
+    tensor = np.random.randint(255, 256, (1000,), dtype=np.uint8)
+    assert np.all(bittensor.tensor(tensor).tensor() == tensor)
+
+    tensor = np.random.randint(2_147_483_646, 2_147_483_647, (1000,), dtype=np.int32)
+    assert np.all(bittensor.tensor(tensor).tensor() == tensor)
+
+    tensor = np.random.randint(
+        9_223_372_036_854_775_806, 9_223_372_036_854_775_807, (1000,), dtype=np.int64
+    )
+    assert np.all(bittensor.tensor(tensor).tensor() == tensor)
+
+    tensor = rng.standard_normal((100,), dtype=np.float32) < 0.5
+    assert np.all(bittensor.tensor(tensor).tensor() == tensor)
+
+
+def test_serialize_all_types_equality_torch(force_legacy_torch_compat_api):
     torchtensor = torch.randn([100], dtype=torch.float16)
     assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor)
 
diff --git a/tests/unit_tests/test_wallet.py b/tests/unit_tests/test_wallet.py
index 0a127ce487..0d0466e344 100644
--- a/tests/unit_tests/test_wallet.py
+++ b/tests/unit_tests/test_wallet.py
@@ -411,6 +411,70 @@ def test_regen_hotkey_from_hex_seed_str(mock_wallet):
         mock_wallet.regenerate_hotkey(seed=seed_str_bad, overwrite=True, suppress=True)
 
 
+@pytest.mark.parametrize(
+    "mnemonic, expected_exception",
+    [
+        # Input is in a string format
+        (
+            "fiscal prevent noise record smile believe quote front weasel book axis legal",
+            None,
+        ),
+        # Input is in a list format (acquired by encapsulating mnemonic arg in a string "" in the cli)
+        (
+            [
+                "fiscal prevent noise record smile believe quote front weasel book axis legal"
+            ],
+            None,
+        ),
+        # Input is in a full list format (aquired by pasting mnemonic arg simply w/o quotes in cli)
+        (
+            [
+                "fiscal",
+                "prevent",
+                "noise",
+                "record",
+                "smile",
+                "believe",
+                "quote",
+                "front",
+                "weasel",
+                "book",
+                "axis",
+                "legal",
+            ],
+            None,
+        ),
+        # Incomplete mnemonic
+        ("word1 word2 word3", ValueError),
+        # No mnemonic added
+        (None, ValueError),
+    ],
+    ids=[
+        "string-format",
+        "list-format-thru-string",
+        "list-format",
+        "incomplete-mnemonic",
+        "no-mnemonic",
+    ],
+)
+def test_regen_coldkey_mnemonic(mock_wallet, mnemonic, expected_exception):
+    """Test the `regenerate_coldkey` method of the wallet class, which regenerates the cold key pair from a mnemonic.
+    We test different input formats of mnemonics and check if the function works as expected.
+    """
+    with patch.object(mock_wallet, "set_coldkey") as mock_set_coldkey, patch.object(
+        mock_wallet, "set_coldkeypub"
+    ) as mock_set_coldkeypub:
+        if expected_exception:
+            with pytest.raises(expected_exception):
+                mock_wallet.regenerate_coldkey(
+                    mnemonic=mnemonic, overwrite=True, suppress=True
+                )
+        else:
+            mock_wallet.regenerate_coldkey(mnemonic=mnemonic)
+            mock_set_coldkey.assert_called_once()
+            mock_set_coldkeypub.assert_called_once()
+
+
 @pytest.mark.parametrize(
     "overwrite, user_input, expected_exception",
     [
diff --git a/tests/unit_tests/utils/test_registration.py b/tests/unit_tests/utils/test_registration.py
new file mode 100644
index 0000000000..a6861783a4
--- /dev/null
+++ b/tests/unit_tests/utils/test_registration.py
@@ -0,0 +1,45 @@
+import pytest
+
+from bittensor.utils.registration import LazyLoadedTorch
+
+
+class MockBittensorLogging:
+    def __init__(self):
+        self.messages = []
+
+    def error(self, message):
+        self.messages.append(message)
+
+
+@pytest.fixture
+def mock_bittensor_logging(monkeypatch):
+    mock_logger = MockBittensorLogging()
+    monkeypatch.setattr("bittensor.btlogging", mock_logger)
+    return mock_logger
+
+
+def test_lazy_loaded_torch__torch_installed(monkeypatch, mock_bittensor_logging):
+    import torch
+
+    lazy_torch = LazyLoadedTorch()
+
+    assert bool(torch) is True
+
+    assert lazy_torch.nn is torch.nn
+    with pytest.raises(AttributeError):
+        lazy_torch.no_such_thing
+
+
+def test_lazy_loaded_torch__no_torch(monkeypatch, mock_bittensor_logging):
+    monkeypatch.setattr("bittensor.utils.registration._get_real_torch", lambda: None)
+
+    torch = LazyLoadedTorch()
+
+    assert not torch
+
+    with pytest.raises(ImportError):
+        torch.some_attribute
+
+    # Check if the error message is logged correctly
+    assert len(mock_bittensor_logging.messages) == 1
+    assert "This command requires torch." in mock_bittensor_logging.messages[0]
diff --git a/tests/unit_tests/utils/test_subtensor.py b/tests/unit_tests/utils/test_subtensor.py
new file mode 100644
index 0000000000..1c1220bcea
--- /dev/null
+++ b/tests/unit_tests/utils/test_subtensor.py
@@ -0,0 +1,99 @@
+# The MIT License (MIT)
+# Copyright © 2022 Opentensor Foundation
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import json
+
+import pytest
+
+import bittensor.utils.subtensor as st_utils
+
+
+class MockPallet:
+    def __init__(self, errors):
+        self.errors = errors
+
+
+@pytest.fixture
+def pallet_with_errors():
+    """Provide a mock pallet with sample errors."""
+    return MockPallet(
+        [
+            {"index": 1, "name": "ErrorOne", "docs": ["Description one."]},
+            {
+                "index": 2,
+                "name": "ErrorTwo",
+                "docs": ["Description two.", "Continued."],
+            },
+        ]
+    )
+
+
+@pytest.fixture
+def empty_pallet():
+    """Provide a mock pallet with no errors."""
+    return MockPallet([])
+
+
+def test_get_errors_from_pallet_with_errors(pallet_with_errors):
+    """Ensure errors are correctly parsed from pallet."""
+    expected = {
+        "1": {"name": "ErrorOne", "description": "Description one."},
+        "2": {"name": "ErrorTwo", "description": "Description two. Continued."},
+    }
+    assert st_utils._get_errors_from_pallet(pallet_with_errors) == expected
+
+
+def test_get_errors_from_pallet_empty(empty_pallet):
+    """Test behavior with an empty list of errors."""
+    assert st_utils._get_errors_from_pallet(empty_pallet) is None
+
+
+def test_save_errors_to_cache(tmp_path):
+    """Ensure that errors are correctly saved to a file."""
+    test_file = tmp_path / "subtensor_errors_map.json"
+    errors = {"1": {"name": "ErrorOne", "description": "Description one."}}
+    st_utils._ERRORS_FILE_PATH = test_file
+    st_utils._save_errors_to_cache("0x123", errors)
+
+    with open(test_file, "r") as file:
+        data = json.load(file)
+        assert data["subtensor_build_id"] == "0x123"
+        assert data["errors"] == errors
+
+
+def test_get_errors_from_cache(tmp_path):
+    """Test retrieval of errors from cache."""
+    test_file = tmp_path / "subtensor_errors_map.json"
+    errors = {"1": {"name": "ErrorOne", "description": "Description one."}}
+
+    st_utils._ERRORS_FILE_PATH = test_file
+    with open(test_file, "w") as file:
+        json.dump({"subtensor_build_id": "0x123", "errors": errors}, file)
+    assert st_utils._get_errors_from_cache() == {
+        "subtensor_build_id": "0x123",
+        "errors": errors,
+    }
+
+
+def test_get_errors_no_cache(mocker, empty_pallet):
+    """Test get_errors function when no cache is available."""
+    mocker.patch("bittensor.utils.subtensor._get_errors_from_cache", return_value=None)
+    mocker.patch("bittensor.utils.subtensor.SubstrateInterface")
+    substrate_mock = mocker.MagicMock()
+    substrate_mock.metadata.get_metadata_pallet.return_value = empty_pallet
+    substrate_mock.metadata[0].value = "0x123"
+    assert st_utils.get_subtensor_errors(substrate_mock) == {}
diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py
index 42c08bd6a7..3c077aba78 100644
--- a/tests/unit_tests/utils/test_utils.py
+++ b/tests/unit_tests/utils/test_utils.py
@@ -18,14 +18,15 @@
 # DEALINGS IN THE SOFTWARE.
 
 import logging
-import torch
+
+import numpy as np
 import bittensor.utils.weight_utils as weight_utils
 import pytest
 
 
 def test_convert_weight_and_uids():
-    uids = torch.tensor(list(range(10)))
-    weights = torch.rand(10)
+    uids = np.arange(10)
+    weights = np.random.rand(10)
     weight_utils.convert_weights_and_uids_for_emit(uids, weights)
 
     # min weight < 0
@@ -45,77 +46,77 @@ def test_convert_weight_and_uids():
         weight_utils.convert_weights_and_uids_for_emit(uids, weights[1:])
 
     # sum(weights) == 0
-    weights = torch.zeros(10)
+    weights = np.zeros(10)
     weight_utils.convert_weights_and_uids_for_emit(uids, weights)
 
     # test for overflow and underflow
     for _ in range(5):
-        uids = torch.tensor(list(range(10)))
-        weights = torch.rand(10)
+        uids = np.arange(10)
+        weights = np.random.rand(10)
         weight_utils.convert_weights_and_uids_for_emit(uids, weights)
 
 
 def test_normalize_with_max_weight():
-    weights = torch.rand(1000)
+    weights = np.random.rand(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.01)
     assert wn.max() <= 0.01
 
-    weights = torch.zeros(1000)
+    weights = np.zeros(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.01)
     assert wn.max() <= 0.01
 
-    weights = torch.rand(1000)
+    weights = np.random.rand(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.02)
     assert wn.max() <= 0.02
 
-    weights = torch.zeros(1000)
+    weights = np.zeros(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.02)
     assert wn.max() <= 0.02
 
-    weights = torch.rand(1000)
+    weights = np.random.rand(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.03)
     assert wn.max() <= 0.03
 
-    weights = torch.zeros(1000)
+    weights = np.zeros(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.03)
     assert wn.max() <= 0.03
 
     # Check for Limit
     limit = 0.001
-    weights = torch.rand(2000)
+    weights = np.random.rand(2000)
     w = weights / weights.sum()
     wn = weight_utils.normalize_max_weight(weights, limit=limit)
-    assert (w.max() >= limit and (limit - wn.max()).abs() < 0.001) or (
+    assert (w.max() >= limit and np.abs(limit - wn.max()) < 0.001) or (
         w.max() < limit and wn.max() < limit
     )
 
     # Check for Zeros
     limit = 0.01
-    weights = torch.zeros(2000)
+    weights = np.zeros(2000)
     wn = weight_utils.normalize_max_weight(weights, limit=limit)
     assert wn.max() == 1 / 2000
 
     # Check for Ordering after normalization
-    weights = torch.rand(100)
+    weights = np.random.rand(100)
     wn = weight_utils.normalize_max_weight(weights, limit=1)
-    assert torch.equal(wn, weights / weights.sum())
+    assert np.array_equal(wn, weights / weights.sum())
 
-    # Check for eplison changes
-    eplison = 0.01
-    weights, _ = torch.sort(torch.rand(100))
+    # Check for epsilon changes
+    epsilon = 0.01
+    weights = np.sort(np.random.rand(100))
     x = weights / weights.sum()
     limit = x[-10]
-    change = eplison * limit
+    change = epsilon * limit
     y = weight_utils.normalize_max_weight(x, limit=limit - change)
     z = weight_utils.normalize_max_weight(x, limit=limit + change)
-    assert (y - z).abs().sum() < eplison
+    assert np.abs(y - z).sum() < epsilon
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, expected",
     [
-        ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], torch.tensor([0.15, 0.05, 0.8])),
-        ("happy-path-2", 4, [1, 3], [50, 50], torch.tensor([0.0, 0.5, 0.0, 0.5])),
+        ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], np.array([0.15, 0.05, 0.8])),
+        ("happy-path-2", 4, [1, 3], [50, 50], np.array([0.0, 0.5, 0.0, 0.5])),
     ],
 )
 def test_convert_weight_uids_and_vals_to_tensor_happy_path(
@@ -125,15 +126,15 @@ def test_convert_weight_uids_and_vals_to_tensor_happy_path(
     result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights)
 
     # Assert
-    assert torch.allclose(result, expected), f"Failed {test_id}"
+    assert np.allclose(result, expected), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, expected",
     [
-        ("edge_case_empty", 5, [], [], torch.zeros(5)),
-        ("edge_case_single", 1, [0], [100], torch.tensor([1.0])),
-        ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], torch.zeros(4)),
+        ("edge_case_empty", 5, [], [], np.zeros(5)),
+        ("edge_case_single", 1, [0], [100], np.array([1.0])),
+        ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], np.zeros(4)),
     ],
 )
 def test_convert_weight_uids_and_vals_to_tensor_edge_cases(
@@ -143,14 +144,14 @@ def test_convert_weight_uids_and_vals_to_tensor_edge_cases(
     result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights)
 
     # Assert
-    assert torch.allclose(result, expected), f"Failed {test_id}"
+    assert np.allclose(result, expected), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, exception",
     [
         ("error-case-mismatched-lengths", 3, [0, 1, 3, 4, 5], [10, 20, 30], IndexError),
-        ("error-case-negative-n", -1, [0, 1], [10, 20], RuntimeError),
+        ("error-case-negative-n", -1, [0, 1], [10, 20], ValueError),
         ("error-case-invalid-uids", 3, [0, 3], [10, 20], IndexError),
     ],
 )
@@ -171,7 +172,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases(
             [0, 1, 2],
             [15, 5, 80],
             [0, 1, 2],
-            torch.tensor([0.15, 0.05, 0.8]),
+            np.array([0.15, 0.05, 0.8]),
         ),
         (
             "happy-path-2",
@@ -179,7 +180,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases(
             [0, 2],
             [300, 300],
             [0, 1, 2],
-            torch.tensor([0.5, 0.0, 0.5]),
+            np.array([0.5, 0.0, 0.5]),
         ),
     ],
 )
@@ -192,7 +193,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths(
     )
 
     # Assert
-    assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
+    assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
@@ -204,7 +205,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths(
             [0],
             [0],
             [0],
-            torch.tensor([0.0]),
+            np.array([0.0]),
         ),  # Single neuron with zero weight
         (
             "edge-2",
@@ -212,7 +213,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths(
             [0, 1],
             [0, 0],
             [0, 1],
-            torch.tensor([0.0, 0.0]),
+            np.array([0.0, 0.0]),
         ),  # All zero weights
     ],
 )
@@ -225,7 +226,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases(
     )
 
     # Assert
-    assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
+    assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
@@ -273,16 +274,16 @@ def test_convert_root_weight_uids_and_vals_to_tensor_error_cases(
             5,
             [1, 3, 4],
             [10, 20, 30],
-            torch.tensor([0, 10, 0, 20, 30], dtype=torch.int64),
+            np.array([0, 10, 0, 20, 30], dtype=np.int64),
         ),
         (
             "happy-path-2",
             3,
             [0, 1, 2],
             [7, 8, 9],
-            torch.tensor([7, 8, 9], dtype=torch.int64),
+            np.array([7, 8, 9], dtype=np.int64),
         ),
-        ("happy-path-3", 4, [2], [15], torch.tensor([0, 0, 15, 0], dtype=torch.int64)),
+        ("happy-path-3", 4, [2], [15], np.array([0, 0, 15, 0], dtype=np.int64)),
     ],
 )
 def test_happy_path(test_id, n, uids, bonds, expected_output):
@@ -290,19 +291,19 @@ def test_happy_path(test_id, n, uids, bonds, expected_output):
     result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)
 
     # Assert
-    assert torch.equal(result, expected_output), f"Failed {test_id}"
+    assert np.array_equal(result, expected_output), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, bonds, expected_output",
     [
-        ("edge-1", 1, [0], [0], torch.tensor([0], dtype=torch.int64)),  # Single element
+        ("edge-1", 1, [0], [0], np.array([0], dtype=np.int64)),  # Single element
         (
             "edge-2",
             10,
             [],
             [],
-            torch.zeros(10, dtype=torch.int64),
+            np.zeros(10, dtype=np.int64),
         ),  # Empty uids and bonds
     ],
 )
@@ -311,14 +312,14 @@ def test_edge_cases(test_id, n, uids, bonds, expected_output):
     result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)
 
     # Assert
-    assert torch.equal(result, expected_output), f"Failed {test_id}"
+    assert np.array_equal(result, expected_output), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, bonds, exception",
     [
         ("error-1", 5, [1, 3, 6], [10, 20, 30], IndexError),  # uid out of bounds
-        ("error-2", -1, [0], [10], RuntimeError),  # Negative number of neurons
+        ("error-2", -1, [0], [10], ValueError),  # Negative number of neurons
     ],
 )
 def test_error_cases(test_id, n, uids, bonds, exception):
diff --git a/tests/unit_tests/utils/test_version.py b/tests/unit_tests/utils/test_version.py
new file mode 100644
index 0000000000..f9760933f3
--- /dev/null
+++ b/tests/unit_tests/utils/test_version.py
@@ -0,0 +1,168 @@
+# The MIT License (MIT)
+# Copyright © 2021 Yuma Rao
+# Copyright © 2022 Opentensor Foundation
+# Copyright © 2023 Opentensor Technologies Inc
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from pathlib import Path
+import pytest
+from freezegun import freeze_time
+from datetime import datetime, timedelta, timezone
+
+from bittensor.utils.version import (
+    VERSION_CHECK_THRESHOLD,
+    VersionCheckError,
+    get_and_save_latest_version,
+    check_version,
+    version_checking,
+)
+from unittest.mock import MagicMock
+from pytest_mock import MockerFixture
+
+
+@pytest.fixture
+def pypi_version():
+    return "6.9.3"
+
+
+@pytest.fixture
+def mock_get_version_from_pypi(mocker: MockerFixture, pypi_version: str):
+    return mocker.patch(
+        "bittensor.utils.version._get_version_from_pypi",
+        return_value=pypi_version,
+        autospec=True,
+    )
+
+
+@pytest.fixture
+def version_file_path(mocker: MockerFixture, tmp_path: Path):
+    file_path = tmp_path / ".version"
+
+    mocker.patch(
+        "bittensor.utils.version._get_version_file_path", return_value=file_path
+    )
+    return file_path
+
+
+def test_get_and_save_latest_version_no_file(
+    mock_get_version_from_pypi: MagicMock, version_file_path: Path, pypi_version: str
+):
+    assert not version_file_path.exists()
+
+    assert get_and_save_latest_version() == pypi_version
+
+    mock_get_version_from_pypi.assert_called_once()
+    assert version_file_path.exists()
+    assert version_file_path.read_text() == pypi_version
+
+
+@pytest.mark.parametrize("elapsed", [0, VERSION_CHECK_THRESHOLD - 5])
+def test_get_and_save_latest_version_file_fresh_check(
+    mock_get_version_from_pypi: MagicMock, version_file_path: Path, elapsed: int
+):
+    now = datetime.now(timezone.utc)
+
+    version_file_path.write_text("6.9.5")
+
+    with freeze_time(now + timedelta(seconds=elapsed)):
+        assert get_and_save_latest_version() == "6.9.5"
+
+    mock_get_version_from_pypi.assert_not_called()
+
+
+def test_get_and_save_latest_version_file_expired_check(
+    mock_get_version_from_pypi: MagicMock, version_file_path: Path, pypi_version: str
+):
+    now = datetime.now(timezone.utc)
+
+    version_file_path.write_text("6.9.5")
+
+    with freeze_time(now + timedelta(seconds=VERSION_CHECK_THRESHOLD + 1)):
+        assert get_and_save_latest_version() == pypi_version
+
+    mock_get_version_from_pypi.assert_called_once()
+    assert version_file_path.read_text() == pypi_version
+
+
+@pytest.mark.parametrize(
+    ("current_version", "latest_version"),
+    [
+        ("6.9.3", "6.9.4"),
+        ("6.9.3a1", "6.9.3a2"),
+        ("6.9.3a1", "6.9.3b1"),
+        ("6.9.3", "6.10"),
+        ("6.9.3", "7.0"),
+        ("6.0.15", "6.1.0"),
+    ],
+)
+def test_check_version_newer_available(
+    mocker: MockerFixture, current_version: str, latest_version: str, capsys
+):
+    mocker.patch("bittensor.utils.version.bittensor.__version__", current_version)
+    mocker.patch(
+        "bittensor.utils.version.get_and_save_latest_version",
+        return_value=latest_version,
+    )
+
+    check_version()
+
+    captured = capsys.readouterr()
+
+    assert "update" in captured.out
+    assert current_version in captured.out
+    assert latest_version in captured.out
+
+
+@pytest.mark.parametrize(
+    ("current_version", "latest_version"),
+    [
+        ("6.9.3", "6.9.3"),
+        ("6.9.3", "6.9.2"),
+        ("6.9.3b", "6.9.3a"),
+    ],
+)
+def test_check_version_up_to_date(
+    mocker: MockerFixture, current_version: str, latest_version: str, capsys
+):
+    mocker.patch("bittensor.utils.version.bittensor.__version__", current_version)
+    mocker.patch(
+        "bittensor.utils.version.get_and_save_latest_version",
+        return_value=latest_version,
+    )
+
+    check_version()
+
+    captured = capsys.readouterr()
+
+    assert captured.out == ""
+
+
+def test_version_checking(mocker: MockerFixture):
+    mock = mocker.patch("bittensor.utils.version.check_version")
+
+    version_checking()
+
+    mock.assert_called_once()
+
+
+def test_version_checking_exception(mocker: MockerFixture):
+    mock = mocker.patch(
+        "bittensor.utils.version.check_version", side_effect=VersionCheckError
+    )
+
+    version_checking()
+
+    mock.assert_called_once()
diff --git a/tests/unit_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py
index 42c08bd6a7..66f3c8127a 100644
--- a/tests/unit_tests/utils/test_weight_utils.py
+++ b/tests/unit_tests/utils/test_weight_utils.py
@@ -18,14 +18,16 @@
 # DEALINGS IN THE SOFTWARE.
 
 import logging
-import torch
+import numpy as np
 import bittensor.utils.weight_utils as weight_utils
 import pytest
 
+from bittensor.utils import torch
+
 
 def test_convert_weight_and_uids():
-    uids = torch.tensor(list(range(10)))
-    weights = torch.rand(10)
+    uids = np.arange(10)
+    weights = np.random.rand(10)
     weight_utils.convert_weights_and_uids_for_emit(uids, weights)
 
     # min weight < 0
@@ -44,6 +46,36 @@ def test_convert_weight_and_uids():
     with pytest.raises(ValueError) as pytest_wrapped_e:
         weight_utils.convert_weights_and_uids_for_emit(uids, weights[1:])
 
+    # sum(weights) == 0
+    weights = np.zeros(10)
+    weight_utils.convert_weights_and_uids_for_emit(uids, weights)
+
+    # test for overflow and underflow
+    for _ in range(5):
+        uids = np.arange(10)
+        weights = np.random.rand(10)
+        weight_utils.convert_weights_and_uids_for_emit(uids, weights)
+
+
+def test_convert_weight_and_uids_torch(force_legacy_torch_compat_api):
+    uids = torch.tensor(list(range(10)))
+    weights = torch.rand(10)
+    weight_utils.convert_weights_and_uids_for_emit(uids, weights)
+
+    # min weight < 0
+    weights[5] = -1
+    with pytest.raises(ValueError) as pytest_wrapped_e:
+        weight_utils.convert_weights_and_uids_for_emit(uids, weights)
+    # min uid < 0
+    weights[5] = 0
+    uids[3] = -1
+    with pytest.raises(ValueError) as pytest_wrapped_e:
+        weight_utils.convert_weights_and_uids_for_emit(uids, weights)
+    # len(uids) != len(weights)
+    uids[3] = 3
+    with pytest.raises(ValueError) as pytest_wrapped_e:
+        weight_utils.convert_weights_and_uids_for_emit(uids, weights[1:])
+
     # sum(weights) == 0
     weights = torch.zeros(10)
     weight_utils.convert_weights_and_uids_for_emit(uids, weights)
@@ -56,6 +88,64 @@ def test_convert_weight_and_uids():
 
 
 def test_normalize_with_max_weight():
+    weights = np.random.rand(1000)
+    wn = weight_utils.normalize_max_weight(weights, limit=0.01)
+    assert wn.max() <= 0.01
+
+    weights = np.zeros(1000)
+    wn = weight_utils.normalize_max_weight(weights, limit=0.01)
+    assert wn.max() <= 0.01
+
+    weights = np.random.rand(1000)
+    wn = weight_utils.normalize_max_weight(weights, limit=0.02)
+    assert wn.max() <= 0.02
+
+    weights = np.zeros(1000)
+    wn = weight_utils.normalize_max_weight(weights, limit=0.02)
+    assert wn.max() <= 0.02
+
+    weights = np.random.rand(1000)
+    wn = weight_utils.normalize_max_weight(weights, limit=0.03)
+    assert wn.max() <= 0.03
+
+    weights = np.zeros(1000)
+    wn = weight_utils.normalize_max_weight(weights, limit=0.03)
+    assert wn.max() <= 0.03
+
+    # Check for Limit
+    limit = 0.001
+    weights = np.random.rand(2000)
+    w = weights / weights.sum()
+    wn = weight_utils.normalize_max_weight(weights, limit=limit)
+    assert abs((w.max() >= limit and (limit - wn.max())) < 0.001) or (
+        w.max() < limit and wn.max() < limit
+    )
+
+    # Check for Zeros
+    limit = 0.01
+    weights = np.zeros(2000)
+    wn = weight_utils.normalize_max_weight(weights, limit=limit)
+    assert wn.max() == 1 / 2000
+
+    # Check for Ordering after normalization
+    weights = np.random.rand(100)
+    wn = weight_utils.normalize_max_weight(weights, limit=1)
+    assert np.array_equal(wn, weights / weights.sum())
+
+    # Check for epsilon changes
+    epsilon = 0.01
+    weights = np.sort(np.random.rand(100))
+    x = weights / weights.sum()
+    limit = x[-10]
+    change = epsilon * limit
+    y = weight_utils.normalize_max_weight(x, limit=limit - change)
+    z = weight_utils.normalize_max_weight(x, limit=limit + change)
+    assert np.abs(y - z).sum() < epsilon
+
+
+def test_normalize_with_max_weight__legacy_torch_api_compat(
+    force_legacy_torch_compat_api,
+):
     weights = torch.rand(1000)
     wn = weight_utils.normalize_max_weight(weights, limit=0.01)
     assert wn.max() <= 0.01
@@ -98,24 +188,24 @@ def test_normalize_with_max_weight():
     # Check for Ordering after normalization
     weights = torch.rand(100)
     wn = weight_utils.normalize_max_weight(weights, limit=1)
-    assert torch.equal(wn, weights / weights.sum())
+    assert torch.isclose(wn, weights / weights.sum(), atol=1e-08, rtol=0).all()
 
-    # Check for eplison changes
-    eplison = 0.01
+    # Check for epsilon changes
+    epsilon = 0.01
     weights, _ = torch.sort(torch.rand(100))
     x = weights / weights.sum()
     limit = x[-10]
-    change = eplison * limit
+    change = epsilon * limit
     y = weight_utils.normalize_max_weight(x, limit=limit - change)
     z = weight_utils.normalize_max_weight(x, limit=limit + change)
-    assert (y - z).abs().sum() < eplison
+    assert (y - z).abs().sum() < epsilon
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, expected",
     [
-        ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], torch.tensor([0.15, 0.05, 0.8])),
-        ("happy-path-2", 4, [1, 3], [50, 50], torch.tensor([0.0, 0.5, 0.0, 0.5])),
+        ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], np.array([0.15, 0.05, 0.8])),
+        ("happy-path-2", 4, [1, 3], [50, 50], np.array([0.0, 0.5, 0.0, 0.5])),
     ],
 )
 def test_convert_weight_uids_and_vals_to_tensor_happy_path(
@@ -124,6 +214,37 @@ def test_convert_weight_uids_and_vals_to_tensor_happy_path(
     # Act
     result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights)
 
+    # Assert
+    assert np.allclose(result, expected), f"Failed {test_id}"
+
+
+@pytest.mark.parametrize(
+    "test_id, n, uids, weights, subnets, expected",
+    [
+        (
+            "happy-path-1",
+            3,
+            [0, 1, 2],
+            [15, 5, 80],
+            [0, 1, 2],
+            torch.tensor([0.15, 0.05, 0.8]),
+        ),
+        (
+            "happy-path-2",
+            3,
+            [0, 2],
+            [300, 300],
+            [0, 1, 2],
+            torch.tensor([0.5, 0.0, 0.5]),
+        ),
+    ],
+)
+def test_convert_weight_uids_and_vals_to_tensor_happy_path_torch(
+    test_id, n, uids, weights, subnets, expected, force_legacy_torch_compat_api
+):
+    # Act
+    result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights)
+
     # Assert
     assert torch.allclose(result, expected), f"Failed {test_id}"
 
@@ -131,9 +252,9 @@ def test_convert_weight_uids_and_vals_to_tensor_happy_path(
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, expected",
     [
-        ("edge_case_empty", 5, [], [], torch.zeros(5)),
-        ("edge_case_single", 1, [0], [100], torch.tensor([1.0])),
-        ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], torch.zeros(4)),
+        ("edge_case_empty", 5, [], [], np.zeros(5)),
+        ("edge_case_single", 1, [0], [100], np.array([1.0])),
+        ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], np.zeros(4)),
     ],
 )
 def test_convert_weight_uids_and_vals_to_tensor_edge_cases(
@@ -143,14 +264,14 @@ def test_convert_weight_uids_and_vals_to_tensor_edge_cases(
     result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights)
 
     # Assert
-    assert torch.allclose(result, expected), f"Failed {test_id}"
+    assert np.allclose(result, expected), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, exception",
     [
         ("error-case-mismatched-lengths", 3, [0, 1, 3, 4, 5], [10, 20, 30], IndexError),
-        ("error-case-negative-n", -1, [0, 1], [10, 20], RuntimeError),
+        ("error-case-negative-n", -1, [0, 1], [10, 20], ValueError),
         ("error-case-invalid-uids", 3, [0, 3], [10, 20], IndexError),
     ],
 )
@@ -171,7 +292,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases(
             [0, 1, 2],
             [15, 5, 80],
             [0, 1, 2],
-            torch.tensor([0.15, 0.05, 0.8]),
+            np.array([0.15, 0.05, 0.8]),
         ),
         (
             "happy-path-2",
@@ -179,7 +300,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases(
             [0, 2],
             [300, 300],
             [0, 1, 2],
-            torch.tensor([0.5, 0.0, 0.5]),
+            np.array([0.5, 0.0, 0.5]),
         ),
     ],
 )
@@ -192,7 +313,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths(
     )
 
     # Assert
-    assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
+    assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
 
 
 @pytest.mark.parametrize(
@@ -217,7 +338,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths(
     ],
 )
 def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases(
-    test_id, n, uids, weights, subnets, expected
+    test_id, n, uids, weights, subnets, expected, force_legacy_torch_compat_api
 ):
     # Act
     result = weight_utils.convert_root_weight_uids_and_vals_to_tensor(
@@ -228,6 +349,39 @@ def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases(
     assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
 
 
+@pytest.mark.parametrize(
+    "test_id, n, uids, weights, subnets, expected",
+    [
+        (
+            "edge-1",
+            1,
+            [0],
+            [0],
+            [0],
+            np.array([0.0]),
+        ),  # Single neuron with zero weight
+        (
+            "edge-2",
+            2,
+            [0, 1],
+            [0, 0],
+            [0, 1],
+            np.array([0.0, 0.0]),
+        ),  # All zero weights
+    ],
+)
+def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases(
+    test_id, n, uids, weights, subnets, expected
+):
+    # Act
+    result = weight_utils.convert_root_weight_uids_and_vals_to_tensor(
+        n, uids, weights, subnets
+    )
+
+    # Assert
+    assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}"
+
+
 @pytest.mark.parametrize(
     "test_id, n, uids, weights, subnets, exception",
     [
@@ -265,6 +419,34 @@ def test_convert_root_weight_uids_and_vals_to_tensor_error_cases(
         )
 
 
+@pytest.mark.parametrize(
+    "test_id, n, uids, bonds, expected_output",
+    [
+        (
+            "happy-path-1",
+            5,
+            [1, 3, 4],
+            [10, 20, 30],
+            np.array([0, 10, 0, 20, 30], dtype=np.int64),
+        ),
+        (
+            "happy-path-2",
+            3,
+            [0, 1, 2],
+            [7, 8, 9],
+            np.array([7, 8, 9], dtype=np.int64),
+        ),
+        ("happy-path-3", 4, [2], [15], np.array([0, 0, 15, 0], dtype=np.int64)),
+    ],
+)
+def test_happy_path(test_id, n, uids, bonds, expected_output):
+    # Act
+    result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)
+
+    # Assert
+    assert np.array_equal(result, expected_output), f"Failed {test_id}"
+
+
 @pytest.mark.parametrize(
     "test_id, n, uids, bonds, expected_output",
     [
@@ -285,7 +467,9 @@ def test_convert_root_weight_uids_and_vals_to_tensor_error_cases(
         ("happy-path-3", 4, [2], [15], torch.tensor([0, 0, 15, 0], dtype=torch.int64)),
     ],
 )
-def test_happy_path(test_id, n, uids, bonds, expected_output):
+def test_happy_path_torch(
+    test_id, n, uids, bonds, expected_output, force_legacy_torch_compat_api
+):
     # Act
     result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)
 
@@ -293,6 +477,27 @@ def test_happy_path(test_id, n, uids, bonds, expected_output):
     assert torch.equal(result, expected_output), f"Failed {test_id}"
 
 
+@pytest.mark.parametrize(
+    "test_id, n, uids, bonds, expected_output",
+    [
+        ("edge-1", 1, [0], [0], np.array([0], dtype=np.int64)),  # Single element
+        (
+            "edge-2",
+            10,
+            [],
+            [],
+            np.zeros(10, dtype=np.int64),
+        ),  # Empty uids and bonds
+    ],
+)
+def test_edge_cases(test_id, n, uids, bonds, expected_output):
+    # Act
+    result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)
+
+    # Assert
+    assert np.array_equal(result, expected_output), f"Failed {test_id}"
+
+
 @pytest.mark.parametrize(
     "test_id, n, uids, bonds, expected_output",
     [
@@ -306,7 +511,9 @@ def test_happy_path(test_id, n, uids, bonds, expected_output):
         ),  # Empty uids and bonds
     ],
 )
-def test_edge_cases(test_id, n, uids, bonds, expected_output):
+def test_edge_cases_torch(
+    test_id, n, uids, bonds, expected_output, force_legacy_torch_compat_api
+):
     # Act
     result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)
 
@@ -318,7 +525,7 @@ def test_edge_cases(test_id, n, uids, bonds, expected_output):
     "test_id, n, uids, bonds, exception",
     [
         ("error-1", 5, [1, 3, 6], [10, 20, 30], IndexError),  # uid out of bounds
-        ("error-2", -1, [0], [10], RuntimeError),  # Negative number of neurons
+        ("error-2", -1, [0], [10], ValueError),  # Negative number of neurons
     ],
 )
 def test_error_cases(test_id, n, uids, bonds, exception):