Repository: mraniki/dxsp
Pull request #783: 💥 breaking: conversion to uv
Author: login: mraniki
Created: 2025-04-27T13:52:18Z | Merged: 2025-04-30T06:41:50Z

## Summary by Sourcery

Migrate project packaging and dependency management from Poetry to Setuptools and UV.

Build:
- Replace Poetry with Setuptools as the build backend.
- Migrate project metadata and dependencies in `pyproject.toml` to PEP 621 format.
- Configure package discovery using the `[tool.setuptools]` table in `pyproject.toml`.
- Update semantic-release configuration to align with the new `pyproject.toml` structure.
- Update versions of pre-commit hooks (Ruff, pre-commit-hooks, Bandit).

CI:
- Update ReadTheDocs build process to use UV for installing documentation dependencies.


Diff:
diff --git a/.github/.pre-commit-config.yaml b/.github/.pre-commit-config.yaml
index 9adff499..ec9dfbd6 100644
--- a/.github/.pre-commit-config.yaml
+++ b/.github/.pre-commit-config.yaml
@@ -1,18 +1,38 @@
+# repos:
+#   - repo: https://github.com/astral-sh/ruff-pre-commit
+#     rev: v0.0.292
+#     hooks:
+#      - id: ruff
+#        args: [--fix, --exit-non-zero-on-fix]
+
+#   - repo: https://github.com/pre-commit/pre-commit-hooks
+#     rev: v4.4.0
+#     hooks:
+#       - id: check-merge-conflict
+#       - id: trailing-whitespace
+
+#   - repo: https://github.com/PyCQA/bandit
+#     rev: '1.7.5'
+#     hooks:
+#     - id: bandit
+#       args: ["-c", "pyproject.toml"]
+
+
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.0.292
+    rev: v0.11.7
     hooks:
      - id: ruff
        args: [--fix, --exit-non-zero-on-fix]
 
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.4.0
+    rev: v5.0.0
     hooks:
       - id: check-merge-conflict
       - id: trailing-whitespace
 
   - repo: https://github.com/PyCQA/bandit
-    rev: '1.7.5'
+    rev: '1.8.3'
     hooks:
     - id: bandit
       args: ["-c", "pyproject.toml"]
\ No newline at end of file
diff --git "a/.github/workflows/\360\237\221\267Flow.yml" "b/.github/workflows/\360\237\221\267Flow.yml"
index 3f2da1f2..d7f163bd 100644
--- "a/.github/workflows/\360\237\221\267Flow.yml"
+++ "b/.github/workflows/\360\237\221\267Flow.yml"
@@ -2,57 +2,30 @@ name: 👷Flow
 
 on:
   push:
-    branches: ["main", "dev"]
+    branches: ["main"]
   pull_request:
   workflow_dispatch:
   merge_group:
- 
-jobs:
-  # lint:
-  #   uses: mraniki/coding_toolset/.github/workflows/🦺Lint.yml@main
-  #   secrets: inherit
-
-  lint_direct:
-    name: 🦺 Lint Direct (dxsp specific)
-    runs-on: ubuntu-latest
-    steps:
-      - name: 📥 Checkout Code
-        uses: actions/checkout@v4
-        with:
-          # Check out the specific commit associated with the PR head or push
-          ref: ${{ github.event.pull_request.head.sha || github.sha }}
-          fetch-depth: 0 # Fetch full history
-
-      - name: 🐍 Set up Python
-        id: setup-python
-        uses: actions/setup-python@v5
-        with:
-          python-version: "3.11"
-          
-      - name: 🛠️ Install Ruff
-        run: |
-          pip install ruff # Install latest or specific version if needed
 
-      - name: 🚨 Lint Specific File
-        run: |
-          echo "Linting specific file: ./dxsp/handler/_client.py"
-          ruff check --fix --no-cache ./dxsp/handler/_client.py
-          echo "Linting specific file finished."
-          # Optionally, add 'ruff check --fix --no-cache .' here if the specific file passes
-          # to check the rest of the project.
+jobs:
+  lint:
+    # uses: mraniki/coding_toolset/.github/workflows/🦺Lint.yml@main
+    uses: mraniki/coding_toolset/.github/workflows/UVLint.yml@main
+    secrets: inherit
 
   test:
-    # needs: [lint]
-    needs: [lint_direct]
-    uses: mraniki/coding_toolset/.github/workflows/🧪Test.yml@main
+    needs: [lint]
+    # uses: mraniki/coding_toolset/.github/workflows/🧪Test.yml@main
+    uses: mraniki/coding_toolset/.github/workflows/UVTest.yml@main
     secrets: inherit
   build:
-    # needs: [lint]
-    needs: [lint_direct]
-    uses: mraniki/coding_toolset/.github/workflows/🐍Build.yml@main
+    needs: [lint]
+    # uses: mraniki/coding_toolset/.github/workflows/🐍Build.yml@main
+    uses: mraniki/coding_toolset/.github/workflows/UVBuild.yml@main
     secrets: inherit
   release:
     needs: [build, test]
-    uses: mraniki/coding_toolset/.github/workflows/📦Release.yml@main
+    # uses: mraniki/coding_toolset/.github/workflows/📦Release.yml@main
+    uses: mraniki/coding_toolset/.github/workflows/UVRelease.yml@main # Use UV-specific release workflow
     secrets: inherit
 
diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml
index 8a89ed4e..d5840fb1 100644
--- a/docs/.readthedocs.yaml
+++ b/docs/.readthedocs.yaml
@@ -4,21 +4,15 @@ build:
   os: "ubuntu-22.04"
   tools:
     python: "3.11"
+    # uv is not directly supported in tools, install via pip below
+    # uv: "latest"
   jobs:
-    # post_create_environment:
-    #   # Install poetry
-    #   # https://python-poetry.org/docs/#installing-manually
-    #   - pip install poetry
-    #   # Tell poetry to not use a virtual environment
-    #   - poetry config virtualenvs.create false
-    # post_install:
-    #   # Install dependencies with 'docs' dependency group
-    #   # https://python-poetry.org/docs/managing-dependencies/#dependency-groups
-    #   - poetry install --with docs
+    # Install uv after the environment is created
     post_create_environment:
-      - python -m pip install poetry
+      - pip install uv
     post_install:
-      - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH python -m poetry install --with docs
+      # Install the package with docs extras using uv
+      - uv pip install -e '.[docs]'
 
 sphinx:
   configuration: docs/conf.py
diff --git a/dxsp/config.py b/dxsp/config.py
index 50bb7ef6..178c161f 100644
--- a/dxsp/config.py
+++ b/dxsp/config.py
@@ -5,18 +5,63 @@
 
 from dynaconf import Dynaconf
 
+# Define the root path of the project library
 ROOT = os.path.dirname(__file__)
+# Define the library's internal default settings path
+DEFAULT_SETTINGS_PATH = os.path.join(ROOT, "default_settings.toml")
 
+# --- Start Modification ---
+# Check for the application's config directory via environment variable
+# e.g., /app or ./ if run locally from tt root
+APP_CONFIG_DIR = os.environ.get("TT_CONFIG_DIR")
+
+# Construct paths to app/user config files if the directory is specified
+talky_settings_path = None
+user_settings_path = None
+secrets_path = None
+op_path = None
+
+if APP_CONFIG_DIR and os.path.isdir(APP_CONFIG_DIR):
+    print(f"DXSP Config: Found TT_CONFIG_DIR: {APP_CONFIG_DIR}")
+    # Use the specific name tt expects for its default settings
+    talky_settings_path = os.path.join(APP_CONFIG_DIR, "tt", "talky_settings.toml")
+    user_settings_path = os.path.join(APP_CONFIG_DIR, "settings.toml")
+    secrets_path = os.path.join(APP_CONFIG_DIR, ".secrets.toml")
+    op_path = os.path.join(APP_CONFIG_DIR, ".op.toml") # If this lib also reads this
+else:
+    print(
+        f"DXSP Config: TT_CONFIG_DIR not set or invalid "
+        f"('{APP_CONFIG_DIR}'). Relying on library defaults."
+    )
+
+# Build the settings_files list dynamically
+# Order: Library Default < TT Default < User Settings < User Secrets
+settings_files = []
+if os.path.exists(DEFAULT_SETTINGS_PATH):
+    settings_files.append(DEFAULT_SETTINGS_PATH)
+else:
+    print(
+        f"DXSP Config: Warning - Library default not found "
+        f"at {DEFAULT_SETTINGS_PATH}"
+    )
+
+if talky_settings_path and os.path.exists(talky_settings_path):
+    settings_files.append(talky_settings_path)
+if user_settings_path and os.path.exists(user_settings_path):
+    settings_files.append(user_settings_path)
+if secrets_path and os.path.exists(secrets_path):
+    settings_files.append(secrets_path)
+if op_path and os.path.exists(op_path):
+    settings_files.append(op_path)
+
+print(f"DXSP Config: Loading settings from: {settings_files}")
+# --- End Modification ---
+
+# Load the settings using the constructed list
 settings = Dynaconf(
     envvar_prefix="TT",
     root_path=os.path.dirname(ROOT),
-    settings_files=[
-        os.path.join(ROOT, "default_settings.toml"),
-        "talky_settings.toml",
-        "settings.toml",
-        ".secrets.toml",
-        ".op.toml",
-    ],
+    settings_files=settings_files, # Use the dynamically built list
     load_dotenv=True,
     merge_enabled=True,
     environments=True,
diff --git a/dxsp/handler/_client.py b/dxsp/handler/_client.py
index 105cbe3e..926b11f6 100644
--- a/dxsp/handler/_client.py
+++ b/dxsp/handler/_client.py
@@ -1,7 +1,3 @@
-"""
-Base DexClient Class   🦄
-"""
-
 import asyncio
 import decimal
 from datetime import datetime, timedelta
@@ -19,94 +15,63 @@
 
 
 class DexClient:
-    """
-    Base DexClient Class for handler base
-
-    Args:
-        **kwargs: Keyword arguments containing the following:
-            - name (str): The name of the client.
-            - protocol (str): The protocol to use (default: "uniswap").
-            - protocol_version (int): The version of the protocol (default: 2).
-            - api_endpoint (str): The API endpoint.
-            - api_key (str): The API key.
-            - rpc (str): The RPC URL.
-            - w3 (Web3): The Web3 instance.
-            - router_contract_addr (str): The router contract address.
-            - factory_contract_addr (str): The factory contract address.
-            - trading_asset_address (str): The trading asset address.
-            - trading_risk_percentage (float): The trading risk percentage.
-            - trading_asset_separator (str): The trading asset separator.
-            - trading_risk_amount (float): The trading risk amount.
-            - trading_slippage (float): The trading slippage.
-            - trading_amount_threshold (float): The trading amount threshold.
-            - block_explorer_url (str): The block explorer URL.
-            - block_explorer_api (str): The block explorer API.
-            - mapping (dict): The mapping.
-            - is_pnl_active (bool): Indicates if PnL is active (default: False).
-            - rotki_report_endpoint (str): The Rotki report endpoint.
-            - follow_wallet (bool): Enable wallet monitoring (default: False).
-            - follow_wallet_address (str): Wallet address to monitor.
-            - follow_wallet_functions (list[str]): Function names to copy
-              (default: ["swapExactTokensForTokens"])
-
-    Returns:
-        None
-
-    Methods:
-        resolve_buy_token
-        resolve_sell_token
-        resolve_token
-        replace_instrument
-        get_order_amount
-        get_quote
-        get_swap
-        make_swap
-        get_account_balance
-        get_trading_asset_balance
-        get_account_position
-        get_account_margin
-        get_account_open_positions
-        get_account_pnl
-        calculate_pnl
-        _run_monitoring_loop
-        _handle_monitored_transaction
-
+    """Handles interactions with decentralized exchanges (DEXs).
+
+    Manages connections to DEXs, handles token resolution, order amount
+    calculations, swaps, and account information retrieval. Supports
+    monitoring a wallet for specific transactions and mirroring those trades.
+
+    Attributes:
+        name (str | None): Optional name for the client instance.
+        protocol (str): The DEX protocol library to use (e.g., "uniswap", "0x").
+        protocol_version (int): The version of the protocol library.
+        api_endpoint (str | None): API endpoint for the DEX (if applicable).
+        api_key (str | None): API key for the DEX (if applicable).
+        rpc (str | None): RPC URL for the blockchain node.
+        w3 (Web3 | None): Web3 instance for blockchain interaction.
+        wallet_address (str | None): The wallet address to use for trading.
+        private_key (str | None): The private key for the wallet address.
+        headers (dict): Headers for HTTP requests (e.g., to ABI URLs).
+        abi_url (str): URL to fetch the standard ERC20 ABI.
+        token_mainnet_list (str | None): Path to a list of mainnet tokens.
+        token_testnet_list (str | None): Path to a list of testnet tokens.
+        token_personal_list (str | None): Path to a personal list of tokens.
+        router_contract_addr (str | None): Address of the DEX router contract.
+        factory_contract_addr (str | None): Address of the DEX factory contract.
+        trading_asset_address (str | None): Address of the primary trading asset (e.g., WETH, USDC).
+        trading_risk_percentage (float | None): Default risk percentage per trade.
+        trading_asset_separator (str | None): Separator used in instrument names (e.g., '-').
+        trading_risk_amount (decimal.Decimal | None): Default fixed risk amount per trade.
+        trading_slippage (float | None): Allowed slippage percentage for swaps.
+        trading_amount_threshold (decimal.Decimal | None): Minimum amount threshold for trades.
+        block_explorer_url (str | None): URL of the blockchain explorer.
+        block_explorer_api (str | None): API URL for the blockchain explorer.
+        mapping (list[dict] | None): Mapping for replacing instrument symbols.
+        is_pnl_active (bool): Flag to enable/disable PnL calculation.
+        rotki_report_endpoint (str | None): URL for the Rotki PnL reporting endpoint.
+        follow_wallet (bool): Flag to enable/disable wallet monitoring.
+        follow_wallet_address (str | None): Address of the wallet to monitor.
+        follow_wallet_functions (list[str]): List of function names to monitor
+            (e.g., ['swapExactTokensForTokens']).
+        client: Placeholder for potential DEX-specific client object.
+        chain (str | None): Chain ID derived from the RPC connection.
+        account_number (str | None): Identifier for the account (chain ID + partial address).
+        contract_utils (ContractUtils | None): Utility for contract interactions.
+        account (AccountUtils | None): Utility for account management.
+        wallet_monitor (WalletMonitor | None): Instance for monitoring wallet transactions.
+        _monitor_task (asyncio.Task | None): Task handle for the monitoring loop.
     """
 
-
     def __init__(self, **kwargs):
-        """
-        Initializes the DexClient object.
+        """Initializes the DexClient with configuration parameters.
 
-        Args:
-            **kwargs: Keyword arguments containing the following:
-                - name (str): The name of the client.
-                - protocol (str): The protocol to use (default: "uniswap").
-                - protocol_version (int): The version of the protocol (default: 2).
-                - api_endpoint (str): The API endpoint.
-                - api_key (str): The API key.
-                - rpc (str): The RPC URL.
-                - w3 (Web3): The Web3 instance.
-                - router_contract_addr (str): The router contract address.
-                - factory_contract_addr (str): The factory contract address.
-                - trading_asset_address (str): The trading asset address.
-                - trading_risk_percentage (float): The trading risk percentage.
-                - trading_asset_separator (str): The trading asset separator.
-                - trading_risk_amount (float): The trading risk amount.
-                - trading_slippage (float): The trading slippage.
-                - trading_amount_threshold (float): The trading amount threshold.
-                - block_explorer_url (str): The block explorer URL.
-                - block_explorer_api (str): The block explorer API.
-                - mapping (dict): The mapping.
-                - is_pnl_active (bool): Indicates if PnL is active (default: False).
-                - rotki_report_endpoint (str): The Rotki report endpoint.
-                - follow_wallet (bool): Enable wallet monitoring (default: False).
-                - follow_wallet_address (str): Wallet address to monitor.
-                - follow_wallet_functions (list[str]): Function names to copy
-                  (default: ["swapExactTokensForTokens"])
+        Sets up Web3 connection, contract/account utilities, and optionally
+        starts the wallet monitoring feature based on the provided keyword
+        arguments.
 
-        Returns:
-            None
+        Args:
+            **kwargs: Keyword arguments for configuration. See class docstring
+                for a list of supported parameters.
         """
         get = kwargs.get
         self.name = get("name", None)
@@ -143,9 +108,7 @@ def __init__(self, **kwargs):
         self.rotki_report_endpoint = get("rotki_report_endpoint", None)
         self.follow_wallet = get("follow_wallet", False)
         self.follow_wallet_address = get("follow_wallet_address", None)
-        self.follow_wallet_functions = get(
-            "follow_wallet_functions", ["swapExactTokensForTokens"]
-        )
+        self.follow_wallet_functions = get("follow_wallet_functions", ["swapExactTokensForTokens"])
 
         self.client = None
         self.chain = None
@@ -158,9 +121,7 @@ def __init__(self, **kwargs):
                 self.w3 = Web3(Web3.HTTPProvider(self.rpc))
                 self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
                 self.w3.eth.set_gas_price_strategy(medium_gas_price_strategy)
-                logger.debug(
-                    f"Chain {self.w3.net.version} - {int(self.w3.net.version, 16)}"
-                )
+                logger.debug(f"Chain {self.w3.net.version} - {int(self.w3.net.version, 16)}")
             except Exception as e:
                 logger.error(f"Invalid RPC URL or response: {e}")
                 self.w3 = None
@@ -194,16 +155,13 @@ def __init__(self, **kwargs):
             if self.w3 and self.follow_wallet_address:
                 try:
                     self.wallet_monitor = WalletMonitor(
-                        w3=self.w3,
-                        address_to_monitor=self.follow_wallet_address
+                        w3=self.w3, address_to_monitor=self.follow_wallet_address
                     )
                     logger.info(
                         f"Wallet monitoring activated for {self.follow_wallet_address} "
                         f"on chain {self.chain}"
                     )
-                    self._monitor_task = asyncio.create_task(
-                        self._run_monitoring_loop()
-                    )
+                    self._monitor_task = asyncio.create_task(self._run_monitoring_loop())
                     logger.info("Wallet monitoring loop started.")
                 except ValueError as e:
                     logger.warning(f"Could not initialize WalletMonitor: {e}")
@@ -218,20 +176,21 @@ def __init__(self, **kwargs):
                 )
                 self.wallet_monitor = None
             else:
-                logger.warning(
-                    "follow_wallet_address is not set."
-                    "Monitoring disabled."
-                )
+                logger.warning("follow_wallet_address is not set.Monitoring disabled.")
                 self.wallet_monitor = None
         else:
             self.wallet_monitor = None
 
     async def _run_monitoring_loop(self):
+        """Runs the wallet monitoring loop asynchronously.
+
+        Listens for new transactions from the `WalletMonitor` instance
+        and schedules `_handle_monitored_transaction` for processing each
+        relevant transaction. Handles exceptions during the loop.
+        """
         logger.info("Entering monitoring loop...")
         if not self.wallet_monitor:
-            logger.error(
-                "Attempted to run WalletMonitor loop, but not initialized."
-            )
+            logger.error("Attempted to run WalletMonitor loop, but not initialized.")
             return
 
         try:
@@ -240,9 +199,7 @@ async def _run_monitoring_loop(self):
                 try:
                     asyncio.create_task(self._handle_monitored_transaction(tx))
                 except Exception as handler_ex:
-                    logger.error(
-                        f"Error scheduling handler for tx {tx.hash.hex()}: {handler_ex}"
-                    )
+                    logger.error(f"Error scheduling handler for tx {tx.hash.hex()}: {handler_ex}")
         except Exception as loop_ex:
             logger.error(f"Exception in monitoring loop: {loop_ex}")
             # Consider restart logic or specific error handling here
@@ -250,6 +207,21 @@ async def _run_monitoring_loop(self):
             logger.info("Exiting monitoring loop.")
 
     async def _handle_monitored_transaction(self, tx: TxData):
+        """Processes a transaction from the monitored wallet.
+
+        This method processes a transaction received from the
+        `WalletMonitor` instance. It filters transactions by the
+        configured router contract address, decodes the function call,
+        identifies the target function, and prepares arguments for a swap.
+
+        Args:
+            tx (TxData): The transaction data to process.
+
+        Returns:
+            None. The method returns early if the transaction is filtered out
+            or if errors occur during processing. It triggers a swap via
+            `get_swap` if a valid monitored transaction is identified.
+        """
         tx_hash = tx.hash.hex()
         logger.info(f"Handling monitored transaction: {tx_hash}")
 
@@ -282,7 +254,7 @@ async def _handle_monitored_transaction(self, tx: TxData):
             func_obj, func_params = router_contract.decode_function_input(tx.input)
             logger.debug(f"[{tx_hash}] Decoded function: {func_obj.fn_name}")
 
-        except ValueError as decode_error: # If input doesn't match ABI
+        except ValueError as decode_error:  # If input doesn't match ABI
             logger.debug(
                 f"[{tx_hash}] Could not decode input data: {decode_error}. "
                 f"Likely not a target function call."
@@ -298,7 +270,7 @@ async def _handle_monitored_transaction(self, tx: TxData):
             # Assuming UniswapV2/PancakeSwap style path parameter for now
             # TODO: Add more robust parameter extraction for different functions
             try:
-                path = func_params.get('path')
+                path = func_params.get("path")
                 if not path or len(path) < 2:
                     logger.warning(
                         f"[{tx_hash}] Invalid or missing 'path' parameter in "
@@ -328,29 +300,21 @@ async def _handle_monitored_transaction(self, tx: TxData):
 
         # Step 6 & 7: Get Token Symbols and Prepare Arguments
         try:
-            sell_token_obj = await self.contract_utils.get_data(
-                contract_address=sell_token_address
-            )
-            buy_token_obj = await self.contract_utils.get_data(
-                contract_address=buy_token_address
-            )
+            sell_token_obj = await self.contract_utils.get_data(contract_address=sell_token_address)
+            buy_token_obj = await self.contract_utils.get_data(contract_address=buy_token_address)
 
             if not sell_token_obj or not sell_token_obj.symbol:
                 logger.error(
-                    f"[{tx_hash}] Could not get symbol for sell token "
-                    f"{sell_token_address}"
+                    f"[{tx_hash}] Could not get symbol for sell token {sell_token_address}"
                 )
                 return
             if not buy_token_obj or not buy_token_obj.symbol:
-                logger.error(
-                    f"[{tx_hash}] Could not get symbol for buy token "
-                    f"{buy_token_address}"
-                )
+                logger.error(f"[{tx_hash}] Could not get symbol for buy token {buy_token_address}")
                 return
 
             sell_symbol = sell_token_obj.symbol
             buy_symbol = buy_token_obj.symbol
-            quantity = self.trading_risk_amount # Use configured risk amount
+            quantity = self.trading_risk_amount  # Use configured risk amount
 
             logger.info(
                 f"[{tx_hash}] Preparing copy trade: SELL {quantity} (risk amount) "
@@ -368,38 +332,38 @@ async def _handle_monitored_transaction(self, tx: TxData):
             #       to avoid redundant symbol lookups within get_swap.
             logger.info(f"[{tx_hash}] Executing copy trade via self.get_swap...")
             swap_result = await self.get_swap(
-                sell_token=sell_symbol,
-                buy_token=buy_symbol,
-                quantity=quantity
+                sell_token=sell_symbol, buy_token=buy_symbol, quantity=quantity
             )
             logger.info(f"[{tx_hash}] Copy trade result: {swap_result}")
 
         except Exception as swap_error:
             logger.error(
-                f"[{tx_hash}] Error copy trade for {sell_symbol}->{buy_symbol}: "
-                f"{swap_error}"
+                f"[{tx_hash}] Error copy trade for {sell_symbol}->{buy_symbol}: {swap_error}"
             )
 
-    async def resolve_token(self, **kwargs):
-        """
-        A function to resolve a token based on the input address or symbol.
-        It takes *args and **kwargs as input parameters.
-        Returns the data associated with the token.
+    async def resolve_token(self, identifier: str):
+        """Resolves a token identifier (symbol or address) to token data.
+
+        Uses `ContractUtils` to fetch token information based on whether the
+        identifier is recognized as a contract address (starts with '0x')
+        or a token symbol. Applies instrument mapping if applicable.
 
         Args:
-            **kwargs: either an address or a symbol.
+            identifier (str): The token symbol (e.g., "WETH") or contract
+                address (e.g., "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").
 
         Returns:
-            Token: The token object containing the data if contract_address is provided.
-            None: If neither symbol nor contract_address is provided.
+            ContractData: An object containing token details (address, symbol,
+                decimals, ABI, etc.) from `ContractUtils`.
+
+        Raises:
+            ValueError: If the token identifier cannot be resolved.
         """
-        logger.debug("Resolving token {}", kwargs)
-        try:
-            (identifier,) = kwargs.values()
-        except ValueError as e:
-            raise ValueError(
-                "Token identification must be an address or a symbol"
-            ) from e
+        logger.debug("Resolving token {}", identifier)
+        # try:
+        #     (identifier,) = kwargs.values()
+        # except ValueError as e:
+        #     raise ValueError("Token identification must be an address or a symbol") from e
 
         # Determine if the input is an address or a symbol
         # Assuming addresses start with '0x'
@@ -411,25 +375,23 @@ async def resolve_token(self, **kwargs):
 
         # Check if the result is not None
         if not result:
-            raise ValueError("Token {} not found", identifier)
+            raise ValueError(f"Token {identifier} not found")
 
         return result
 
-    async def replace_instrument(self, instrument):
-        """
-        Replace instrument by an alternative instrument, if the
-        instrument is not in the mapping, it will be ignored.
-        Mapping, define in settings as TOML or .env variable.
-        It is a list of dictionaries such as:
-        mapping = [
-            { id = "BTC", alt = "WBTC" ,enable = true },
-        ]
+    async def replace_instrument(self, instrument: str) -> str:
+        """Replaces an instrument symbol using the configured mapping.
+
+        Checks if the `self.mapping` list contains an entry for the given
+        `instrument` and returns the corresponding alternative symbol (`alt`)
+        if found and enabled. Otherwise, returns the original instrument.
 
         Args:
-            instrument (str):
+            instrument (str): The instrument symbol to potentially replace.
 
         Returns:
-            dict
+            str: The replaced instrument symbol or the original symbol if no
+                 mapping is found or applicable.
         """
         logger.debug("Replace instrument: {}", instrument)
         if self.mapping is None:
@@ -443,184 +405,294 @@ async def replace_instrument(self, instrument):
         return instrument
 
     async def get_order_amount(
-        self, sell_token, wallet_address, quantity, is_percentage=True
-    ):
-        """
-        Calculate the order amount based on the sell token,
-        wallet address, quantity, and whether it is a percentage.
+        self, sell_token, wallet_address: str, quantity: float | decimal.Decimal,
+        is_percentage: bool = True
+    ) -> decimal.Decimal:
+        """Calculates the order amount in the sell token's base units.
+
+        Calculates the amount based on a percentage of the wallet's balance
+        of the `sell_token` or a fixed `quantity`.
 
         Args:
-            sell_token (SellToken): The sell token object.
-            wallet_address (str): The wallet address.
-            quantity (float): The quantity of the sell token.
-            is_percentage (bool, optional):
-            Flag indicating whether the quantity is a percentage. Defaults to True.
+            sell_token: The `ContractData` object for the token being sold.
+            wallet_address (str): The address of the wallet holding the token.
+            quantity (float | decimal.Decimal): The percentage (e.g., 1 for 1%)
+                or the fixed amount of the token to sell.
+            is_percentage (bool): If True, `quantity` is treated as a percentage
+                of the balance. If False, `quantity` is a fixed amount.
 
         Returns:
-            float: The calculated order amount.
+            decimal.Decimal: The calculated order amount in the token's base
+                units, or Decimal(0) if the balance is insufficient or zero.
         """
-        logger.debug("get order amount {} {} {}", sell_token, wallet_address, quantity)
-        logger.debug("Protocol", self.contract_utils.platform)
+        logger.debug("get order amount {} {} {}", sell_token.symbol, wallet_address, quantity)
+        logger.debug("Protocol {}", self.contract_utils.platform)
         balance = await sell_token.get_token_balance(wallet_address)
-        logger.debug("Balance {}", balance)
-        if not is_percentage and balance:
-            logger.debug("Quantity {}", quantity)
-            return quantity
-
-        if balance:
-            risk_percentage = float(quantity) / 100
-            logger.debug("Risk percentage {}", risk_percentage)
-            amount = balance * decimal.Decimal(risk_percentage)
-            logger.debug("Amount {}", amount)
-            if (
-                isinstance(amount, decimal.Decimal)
-                and amount > self.trading_amount_threshold
-            ):
-                logger.debug("Amount {}", amount)
-                return amount
-
-        return 0
+        logger.debug("Balance {} {}", balance, sell_token.symbol)
+
+        if not balance or balance == 0:
+            logger.warning("Balance is zero for {}", sell_token.symbol)
+            return decimal.Decimal(0)
+
+        if not is_percentage:
+            # If quantity is fixed, return it directly if balance is sufficient
+            fixed_amount = decimal.Decimal(quantity)
+            if balance >= fixed_amount:
+                logger.debug("Using fixed quantity: {} {}", fixed_amount, sell_token.symbol)
+                return fixed_amount
+            else:
+                logger.warning(
+                    f"Insufficient balance ({balance} {sell_token.symbol}) "
+                    f"for fixed quantity ({fixed_amount} {sell_token.symbol})"
+                )
+                return decimal.Decimal(0)
+
+        # Calculate amount based on percentage
+        risk_percentage = decimal.Decimal(quantity) / 100
+        logger.debug("Risk percentage {}", risk_percentage)
+        amount = balance * risk_percentage
+        logger.debug("Calculated amount {} {}", amount, sell_token.symbol)
+
+        # Check against threshold (assuming threshold is in the token's units, not wei)
+        # TODO: Clarify if trading_amount_threshold is in native units or wei
+        threshold = self.trading_amount_threshold or decimal.Decimal(0)
+        if amount > threshold:
+            logger.debug("Amount {} > Threshold {}", amount, threshold)
+            return amount
+        else:
+            logger.warning(
+                f"Calculated amount {amount} {sell_token.symbol} is below "
+                f"threshold {threshold}"
+             )
+            return decimal.Decimal(0)
 
     async def get_quote(
         self,
-        buy_address=None,
-        buy_symbol=None,
-        sell_address=None,
-        sell_symbol=None,
-        amount=1,
+        buy_address: str | None = None,
+        buy_symbol: str | None = None,
+        sell_address: str | None = None,
+        sell_symbol: str | None = None,
+        amount: int = 1,
     ):
-        """
-        Get a quote method for specific protocol
+        """Fetches a quote for a potential swap (Not Implemented).
 
-        """
+        Args:
+            buy_address (str | None): Contract address of the token to buy.
+            buy_symbol (str | None): Symbol of the token to buy.
+            sell_address (str | None): Contract address of the token to sell.
+            sell_symbol (str | None): Symbol of the token to sell.
+            amount (int): The amount of the sell token (in its base unit)
+                for the quote. Defaults to 1.
 
-    async def get_swap(self, sell_token=None, buy_token=None, quantity=1):
+        Returns:
+            NotImplemented: This method is not yet implemented.
         """
-        Execute a swap
+        pass
 
-        Args:
-            sell_token (str): The sell token.
-            buy_token (str): The buy token.
-            quantity (int): The quantity of tokens.
+    async def get_swap(
+        self, sell_token: str | None = None, buy_token: str | None = None,
+        quantity: float | decimal.Decimal = 1
+    ) -> str:
+        """Executes a swap between two tokens using specified symbols.
 
-        Returns:
-            transactionHash
+        Resolves token symbols to addresses, calculates the order amount
+        based on the configured risk settings (percentage or fixed amount),
+        approves the router contract if necessary (e.g., for 0x protocol),
+        constructs and executes the swap transaction, waits for the receipt,
+        and returns a confirmation message or error.
 
+        Args:
+            sell_token (str | None): Symbol of the token to sell (e.g., "WETH").
+            buy_token (str | None): Symbol of the token to buy (e.g., "USDC").
+            quantity (float | decimal.Decimal): The quantity or percentage
+                to swap, interpreted based on `trading_risk_percentage` vs
+                `trading_risk_amount` settings. Defaults to 1 (interpreted
+                based on config).
 
+        Returns:
+            str: A confirmation message with the transaction hash on success,
+                 or an error message string starting with "⚠️" on failure.
         """
         try:
-            logger.debug("get swap {} {} {}", sell_token, buy_token, quantity)
-            logger.debug("Protocol", self.contract_utils.platform)
-            sell_token = await self.contract_utils.get_data(symbol=sell_token)
-            logger.debug("sell token {}", sell_token)
-            buy_token = await self.contract_utils.get_data(symbol=buy_token)
-            logger.debug("buy token {}", buy_token)
-
-            sell_amount = await self.get_order_amount(
-                sell_token, self.account.wallet_address, quantity
+            # Determine quantity and if it's a percentage based on config
+            is_percentage = self.trading_risk_percentage is not None
+            trade_quantity = (
+                self.trading_risk_percentage if is_percentage else self.trading_risk_amount
             )
-            if not sell_amount or sell_amount == 0:
-                logger.error("sell amount {}", sell_amount)
-                return f"⚠️ sell amount failed {sell_amount}"
+            if trade_quantity is None:
+                 # Fallback if neither percentage nor amount is set
+                 logger.warning(
+                     "Neither trading_risk_percentage nor trading_risk_amount is set. "
+                     "Defaulting to quantity=1 as percentage."
+                 )
+                 trade_quantity = decimal.Decimal(1)
+                 is_percentage = True
+            else:
+                trade_quantity = decimal.Decimal(trade_quantity)
+
 
-            sell_token_amount_wei = decimal.Decimal(sell_amount) * (
-                decimal.Decimal("10") ** int(sell_token.decimals)
+            logger.debug(
+                f"get swap: Sell {sell_token}, Buy {buy_token}, "
+                f"Quantity Arg: {quantity} -> Using Config: {trade_quantity} "
+                f"{'%' if is_percentage else ''}"
             )
-            if self.protocol == "0x":
-                await self.account.get_approve(sell_token.address)
+            logger.debug("Protocol {}", self.contract_utils.platform)
 
-            order_amount = int(
-                sell_token_amount_wei * decimal.Decimal((self.trading_slippage / 100))
+            # Resolve tokens
+            sell_token_obj = await self.resolve_token(identifier=sell_token)
+            logger.debug("Sell token data: {}", sell_token_obj)
+            buy_token_obj = await self.resolve_token(identifier=buy_token)
+            logger.debug("Buy token data: {}", buy_token_obj)
+
+            # Calculate order amount in sell token's native units
+            sell_amount_native = await self.get_order_amount(
+                sell_token_obj, self.account.wallet_address, trade_quantity, is_percentage
+            )
+
+            if not sell_amount_native or sell_amount_native == 0:
+                logger.error("Calculated sell amount is zero or failed: {}", sell_amount_native)
+                return "⚠️ Sell amount calculation failed or resulted in zero."
+
+            logger.info(f"Calculated sell amount: {sell_amount_native} {sell_token_obj.symbol}")
+
+            # Convert native amount to Wei
+            sell_token_amount_wei = int(
+                sell_amount_native * (decimal.Decimal("10") ** int(sell_token_obj.decimals))
             )
-            logger.debug("order amount {}", order_amount)
+            logger.debug(f"Sell amount in Wei: {sell_token_amount_wei}")
 
+
+            # Approve router if needed (e.g., 0x)
+            if self.protocol == "0x":
+                logger.info(f"Approving router for {sell_token_obj.symbol}...")
+                await self.account.get_approve(sell_token_obj.address)
+                logger.info("Approval likely sent (check logs/explorer).")
+
+
+            # TODO: Slippage calculation might need refinement depending on protocol
+            # This looks like it's applying slippage *before* getting the quote/swap params.
+            # Usually, slippage is applied *after* getting a quote to set a minimum receive amount.
+            # Renaming `order_amount` to `amount_in_wei_for_swap` for clarity.
+            # The actual amount used in the swap call might differ based on the protocol.
+            # For UniV2 swapExactTokens*, this is `amountIn`.
+            amount_in_wei_for_swap = sell_token_amount_wei
+            # amount_min_out = int(
+            #    sell_token_amount_wei * decimal.Decimal(1 - (self.trading_slippage / 100))
+            # ) # Example slippage calc
+            # logger.debug("Slippage adjusted min out (example): {}", amount_min_out)
+            logger.debug("Amount for swap function (amountIn): {}", amount_in_wei_for_swap)
+
+            # Make the swap transaction
             order = await self.make_swap(
-                sell_token.address, buy_token.address, order_amount
+                sell_token_obj.address, buy_token_obj.address, amount_in_wei_for_swap
             )
 
             if not order:
-                logger.error("swap order not executed")
-                return "⚠️ order execution failed"
+                logger.error("Swap order creation/fetch failed in make_swap.")
+                return "⚠️ Order creation/fetch failed."
 
-            signed_order = await self.account.get_sign(order)
-            order_hash = str(self.w3.to_hex(signed_order))
-            logger.debug(order_hash)
-            receipt = self.w3.eth.wait_for_transaction_receipt(order_hash)
-            logger.debug(receipt)
+            # Sign and send transaction
+            signed_tx_hash = await self.account.get_sign(order) # Assuming get_sign returns the hash
+            # signed_order = await self.account.get_sign(order) # If get_sign returns signed tx
+            # order_hash = str(
+            #     self.w3.eth.send_raw_transaction(signed_order.rawTransaction).hex()
+            # ) # If signing locally
 
-            if receipt["status"] != 1:
-                logger.error("receipt failed")
+            logger.info(f"Transaction submitted: {signed_tx_hash}")
 
-            return await self.contract_utils.get_confirmation(
-                receipt["transactionHash"]
-            )
+            # Wait for receipt
+            receipt = self.w3.eth.wait_for_transaction_receipt(signed_tx_hash)
+            logger.debug("Transaction receipt: {}", receipt)
 
+            if receipt["status"] != 1:
+                logger.error(f"Transaction failed! Receipt: {receipt}")
+                # Consider returning more info from receipt if available
+                return f"⚠️ Transaction failed (Status 0). Hash: {signed_tx_hash}"
+
+            # Return confirmation (e.g., explorer link)
+            confirmation = await self.contract_utils.get_confirmation(receipt["transactionHash"])
+            logger.info(f"Swap successful: {confirmation}")
+            return confirmation
+
+        except ValueError as ve:
+             logger.error(f"Value error during swap: {ve}")
+             return f"⚠️ {str(ve)}"
         except Exception as error:
-            logger.debug(error)
-            return f"⚠️ {str(error)}"
+            # Use exception for stack trace
+            logger.exception(f"Unexpected error during swap: {error}")
+            return f"⚠️ Unexpected error: {str(error)}"
 
-    async def make_swap(self, sell_address, buy_address, amount):
-        """
-        Make a swap method for specific protocol
+    async def make_swap(self, sell_address: str, buy_address: str, amount: int):
+        """Constructs the swap transaction data (Not Implemented).
 
-        """
+        This method should interact with the specific DEX protocol's API or
+        contracts to prepare the transaction parameters for a swap.
+
+        Args:
+            sell_address (str): Contract address of the token to sell.
+            buy_address (str): Contract address of the token to buy.
+            amount (int): The amount of the sell token in Wei.
 
-    async def get_account_balance(self):
+        Returns:
+            NotImplemented: This method is not yet implemented.
+            Should return the transaction data (e.g., dict for web3 `send_transaction`).
         """
-        Retrieves the account balance.
+        pass
+
+    async def get_account_balance(self) -> str | None:
+        """Fetches the native balance (e.g., ETH, BNB) of the client's wallet.
 
-        :return: The account balance.
-        :rtype: float
+        Returns:
+            str | None: The native balance as a string, or None if unavailable.
         """
         return await self.account.get_account_balance()
 
-    async def get_trading_asset_balance(self):
-        """
-        Retrieves the trading asset balance for the current account.
+    async def get_trading_asset_balance(self) -> str | None:
+        """Fetches the balance of the configured `trading_asset_address`.
 
-        :return: A dictionary containing the trading asset balance.
-                The dictionary has the following keys:
-                - 'asset': The asset symbol.
-                - 'free': The free balance of the asset.
-                - 'locked': The locked balance of the asset.
+        Returns:
+            str | None: The trading asset balance as a string, or None if
+                unavailable or not configured.
         """
         return await self.account.get_trading_asset_balance()
 
     async def get_account_position(self):
-        """
-        Retrieves the account position.
+        """Fetches the account's overall position (Not Implemented in AccountUtils).
 
-        :return: The account position.
-        :rtype: AccountPosition
+        Returns:
+            NotImplemented: Depends on the implementation in `AccountUtils`.
         """
         return await self.account.get_account_position()
 
     async def get_account_margin(self):
-        """
-        Retrieves the account margin.
+        """Fetches the account's margin details (Not Implemented in AccountUtils).
 
-        :return: The account margin.
-        :rtype: float
+        Returns:
+            NotImplemented: Depends on the implementation in `AccountUtils`.
         """
         return await self.account.get_account_margin()
 
     async def get_account_open_positions(self):
-        """
-        Retrieves the open positions of the account.
+        """Fetches the account's open positions (Not Implemented in AccountUtils).
 
-        :return: A list of open positions in the account.
+        Returns:
+            NotImplemented: Depends on the implementation in `AccountUtils`.
         """
         return await self.account.get_account_open_positions()
 
-    async def get_account_pnl(self, period=None):
-        """
-        Return account pnl.
+    async def get_account_pnl(self, period: str | None = None) -> float | dict:
+        """Calculates Profit and Loss (PnL) for the account for a given period.
+
+        Delegates the calculation to `calculate_pnl` if PnL tracking is active.
+        Determines the start date based on the `period` identifier.
 
         Args:
-            None
+            period (str | None): The period identifier ('W' for week, 'M' for
+                month, 'Y' for year, None for today). Defaults to None (today).
 
         Returns:
-            pnl
+            float | dict: The calculated PnL. Returns 0 if PnL is inactive.
+                The return type depends on `calculate_pnl` (expected dict or 0).
         """
         today = datetime.now().date()
         if period is None:
@@ -635,27 +707,30 @@ async def get_account_pnl(self, period=None):
             return 0
         return self.calculate_pnl(start_date) if self.is_pnl_active else 0
 
-    async def calculate_pnl(self, period=None):
-        """
-        Calculate the PnL for a given period.
-        via https://rotki.readthedocs.io/en/latest/api.html
+    async def calculate_pnl(self, period: str | None = None) -> dict | int:
+        """Calculates PnL by querying the Rotki reporting endpoint.
 
-        Parameters:
-            period (str):
-            The period for which to calculate PnL ('W', 'M', 'Y', or None)
+        Sends a GET request to the configured `rotki_report_endpoint`
+        with the optional period parameter. Parses the JSON response
+        to extract and sum 'free' amounts from different categories.
+
+        Args:
+            period (str | None): The period string to pass to the Rotki API.
+                Defaults to None.
 
         Returns:
-            pnl: The calculated PnL value.
+            dict | int: A dictionary containing the sum of 'free' PnL values
+                per category (e.g., 'trade', 'fee') on success. Returns 0 if
+                the endpoint is not configured, the request fails, or the
+                response format is unexpected.
         """
-
         if self.rotki_report_endpoint is None:
+            logger.warning("Rotki report endpoint not configured. Cannot calculate PnL.")
             return 0
         params = {"period": period} if period else {}
 
         async with aiohttp.ClientSession() as session:
-            async with session.get(
-                self.rotki_report_endpoint, params=params
-            ) as response:
+            async with session.get(self.rotki_report_endpoint, params=params) as response:
                 if response.status != 200:
                     logger.error(f"Received non-200 status code: {response.status}")
                     return 0
diff --git a/dxsp/handler/zerox.py b/dxsp/handler/zerox.py
index 3ada9281..5501bf72 100644
--- a/dxsp/handler/zerox.py
+++ b/dxsp/handler/zerox.py
@@ -61,14 +61,14 @@ async def get_quote(
             )
             # Resolve buy_token
             buy_token = await self.resolve_token(
-                address_or_symbol=buy_address
+                identifier=buy_address
                 or buy_symbol
                 or self.trading_asset_address
             )
 
             # Resolve sell_token
             sell_token = await self.resolve_token(
-                address_or_symbol=sell_address or sell_symbol
+                identifier=sell_address or sell_symbol
             )
             if not buy_token or not sell_token:
                 logger.error("Buy or sell token not resolved.")
@@ -111,7 +111,7 @@ async def get_quote(
             if price:
                  # Log issues but still return the price
                 if "issues" in response or "validationErrors" in response:
-                    logger.warning(f"0x API returned issues/errors but price is available: {response}")
+                    logger.warning(f"0x API issues/errors present but price available: {response}")
                 return float(price)
 
             # If no price found, *then* report issues/errors
diff --git a/dxsp/main.py b/dxsp/main.py
index f11fbbe2..9d89b418 100644
--- a/dxsp/main.py
+++ b/dxsp/main.py
@@ -66,8 +66,12 @@ def __init__(self):
             logger.info("Module is disabled. No Client will be created.")
             return
         self.clients = []
-        # Create a client for each client in settings.dex
-        for name, client_config in settings.dex.items():
+        # Use .get() for safer access to the nested table
+        dex_config_table = settings.get('dex', {})
+        if not dex_config_table:
+             logger.warning("No 'dex' configuration table found in settings.")
+        # Create a client for each client in the retrieved table
+        for name, client_config in dex_config_table.items():
             if (
                 # Skip empty client configs
                 client_config is None
diff --git a/handler/_client.py b/handler/_client.py
deleted file mode 100644
index 53e4db21..00000000
--- a/handler/_client.py
+++ /dev/null
@@ -1,36 +0,0 @@
-                - follow_wallet (bool): Enable wallet monitoring (default: False).
-                - follow_wallet_address (str): Wallet address to monitor.
-                - follow_wallet_functions (list[str]): Function names to copy 
-                  (default: ["swapExactTokensForTokens"])
-
-        Returns:
-            None
-
-        self.follow_wallet = get("follow_wallet", False)
-        self.follow_wallet_address = get("follow_wallet_address", None)
-        # NEW: Get configurable list of functions to follow
-        default_funcs = ["swapExactTokensForTokens"]
-        self.follow_wallet_functions = get("follow_wallet_functions", default_funcs)
-
-        self.client = None
-
-        if self.follow_wallet:
-            try:
-                self.wallet_monitor = WalletMonitor(
-                    w3=self.w3,
-                    address_to_monitor=self.follow_wallet_address
-                )
-                logger.info(
-                    f"Wallet monitoring activated for "
-                    f"{self.follow_wallet_address} on chain {self.chain}"
-                )
-                self._monitor_task = asyncio.create_task(
-                    self._run_monitoring_loop()
-                )
-                logger.info("Wallet monitoring loop started.")
-            except ValueError as e:
-                logger.error(f"Error activating wallet monitoring: {e}")
-
-    def _run_monitoring_loop(self):
-        # Implementation of _run_monitoring_loop method
-        pass 
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 79d5ca29..9f95d505 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,41 +1,68 @@
 [build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
 
-[tool.poetry]
+[project]
 name = "dxsp"
 version = "12.0.1"
 description = "DXSP (DeX SwaP), A defi swap helper package. Swap made easy."
-authors = ["mraniki <8766259+mraniki@users.noreply.github.com>"]
-license = "MIT License"
+authors = [
+  { name = "mraniki", email = "8766259+mraniki@users.noreply.github.com" },
+]
+license = "MIT"
 readme = "README.md"
-packages = [
-    {include = "dxsp"}
+keywords = ["dex", "swap", "crypto", "exchange", "defi"]
+requires-python = ">=3.10"
+classifiers = [
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+]
+dependencies = [
+  "dynaconf>=3.2.0",
+  "loguru>=0.6.0",
+  "pycoingecko==3.2.0",
+  "requests>=2.31.0",
+  "web3==6.20.4",
+  "uniswap-python==0.7.1",
+  "kwenta==1.2.0",
 ]
 
-
-
-[tool.poetry.urls]
+[project.urls]
+"Homepage" = "https://github.com/mraniki/dxsp"
 "Changelog" =  "https://github.com/mraniki/dxsp/blob/dev/CHANGELOG.rst"
 "Support" =  "https://github.com/mraniki/dxsp/discussions"
 "Issues" =  "https://github.com/mraniki/dxsp/issues"
 
 
-[tool.poetry.dependencies]
-python = "^3.10"
-dynaconf = ">=3.2.0"
-loguru = ">=0.6.0"
-pycoingecko = "3.2.0"
-requests = "^2.31.0"
-web3 = "6.20.4"
-uniswap-python = "0.7.1"
-kwenta = "1.2.0"
-
+[project.optional-dependencies]
+dev = [
+  "python-semantic-release>=8.0.8",
+  "ruff~=0.11",
+  "pre-commit~=4.0",
+]
+test = [
+  "pytest~=8.2",
+  "pytest-cov~=6.0",
+  "pytest-asyncio~=0.25",
+  "pytest-mock~=3.11",
+  "pytest-loguru~=0.4",
+  "eth_tester~=0.9.0b2",
+]
+docs = [
+  "sphinx==7.4.7",
+  "pydata-sphinx-theme==0.16.1",
+  "sphinx-hoverxref==1.4.2",
+  "sphinx_copybutton==0.5.2",
+  "myst_parser==4.0.1",
+  "sphinx_design~=0.6",
+]
 
-[tool.poetry.group.dev.dependencies]
-python-semantic-release = ">=8.0.8"
-ruff = "^0.11.0"
-pre-commit = "^4.0.0"
+[tool.setuptools.packages.find]
+where = ["."] 
+include = ["dxsp*"] 
+exclude = ["tests*", "docs*", "examples*"]
 
 
 [tool.ruff]
@@ -47,9 +74,9 @@ line-length = 100
 
 [tool.ruff.lint]
 select = [
-  "E",  # pycodestyle
-  "F",  # pyflakes
-  "I",  # isort
+  "E",
+  "F",
+  "I",
   "W"
 ]
 
@@ -62,18 +89,8 @@ fixable = ["ALL"]
 [tool.ruff.format]
 # Like Black, use double quotes for strings.
 quote-style = "double"
-indent-style = "tab"
 docstring-code-format = true
 
-[tool.poetry.group.test.dependencies]
-pytest = "^8.2"
-pytest-cov = "^6.0.0"
-pytest-asyncio = "^0.25.0"
-pytest-mock = "^3.11.1"
-pytest-loguru = "^0.4.0"
-eth_tester = "^0.9.0b2"
-# eth-tester = "^0.12.0b1"
-
 [tool.pytest.ini_options]
 pythonpath = "."
 testpaths = "tests"
@@ -85,6 +102,7 @@ addopts = """
 --show-capture=stderr
 """
 asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
 
 [tool.coverage.run]
 omit = [
@@ -100,26 +118,14 @@ skips = ["B101","B104"]
 
 
 
-[tool.poetry.group.docs]
-optional = true
-
-[tool.poetry.group.docs.dependencies]
-sphinx = "7.4.7"
-pydata-sphinx-theme = "0.16.1"
-sphinx-hoverxref = "1.4.2"
-sphinx_copybutton = "0.5.2"
-myst_parser = "4.0.1"
-sphinx_design = "^0.6.0"
-
 [tool.semantic_release]
 tag_format = "v{version}"
 commit_parser = "emoji"
-build_command = "pip install poetry && poetry build"
 version_variables = [
     "dxsp/__init__.py:__version__",
 ]
 version_toml = [
-   "pyproject.toml:tool.poetry.version",
+   "pyproject.toml:project.version",
 ]
 
 [tool.semantic_release.commit_parser_options]
diff --git a/tests/test_unit_client.py b/tests/test_unit_client.py
index c2a1bcce..e6986159 100755
--- a/tests/test_unit_client.py
+++ b/tests/test_unit_client.py
@@ -42,25 +42,25 @@ def client_zero_x_fixture(dex):
 @pytest.mark.asyncio
 async def test_resolve_token_address(dex_client):
     result = await dex_client.resolve_token(
-        address="0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
+        identifier="0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
     )
     assert result.symbol == "WBTC"
 
 
 # @pytest.mark.asyncio
 # async def test_resolve_token_symbol(dex_client):
-#     result = await dex_client.resolve_token(symbol="LINK")
+#     result = await dex_client.resolve_token(identifier="LINK")
 #     assert result.address == "0x514910771AF9Ca656af840dff83E8264EcF986CA"
 
 @pytest.mark.asyncio
 async def test_resolve_token_symbol(dex_client_bsc):
-    result = await dex_client_bsc.resolve_token(symbol="TON")
+    result = await dex_client_bsc.resolve_token(identifier="TON")
     assert result.address == "0x76A797A59Ba2C17726896976B7B3747BfD1d220f"
 
 
 @pytest.mark.asyncio
 async def test_resolve_token(dex_client):
-    result = await dex_client.resolve_token(symbol="PEPE")
+    result = await dex_client.resolve_token(identifier="PEPE")
     assert result.address == "0x6982508145454Ce325dDbE47a25d4ec3d2311933"
     assert result.decimals == 18
     # assert result.get_contract_function is not None
@@ -69,7 +69,7 @@ async def test_resolve_token(dex_client):
 
 @pytest.mark.asyncio
 async def test_resolve_token_error(dex_client):
-    with pytest.raises(ValueError):
+    with pytest.raises(TypeError, match="missing 1 required positional argument: 'identifier'"):
         await dex_client.resolve_token()
 
 


Reviews:
user: login: sourcery-ai[bot]
body: Hey @mraniki - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Could you clarify in the description what aspect of this change is considered 'breaking' for users or contributors?
- The pre-commit configuration file contains commented-out sections that could potentially be removed.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T13:53:07Z
user: login: llamapreview[bot]
body: # Auto Pull Request Review from LlamaPReview

## 1. Overview
### 1.1 Core Changes
- **Primary purpose and scope**: Migrate the project's build system and dependency management from Poetry to Setuptools and UV. This involves updating configuration files (`pyproject.toml`, CI workflows, ReadTheDocs config, pre-commit hooks).
- **Key components modified**: `pyproject.toml`, `.github/workflows/👷Flow.yml`, `docs/.readthedocs.yaml`, `.github/.pre-commit-config.yaml`.
- **Cross-component impacts**: Affects the build process, dependency installation, CI/CD pipeline execution (linting, testing, building, releasing), documentation generation, and developer setup/workflow.
- **Business value alignment**: Aims to standardize packaging using PEP 621, potentially improve dependency installation speed with UV, and simplify the overall dependency management workflow. However, this is marked as a breaking change, requiring users and contributors to adapt.

### 1.2 Technical Architecture 
- **System design modifications**: Changes the core build backend from `poetry.core.masonry.api` to `setuptools.build_meta`.
- **Component interaction changes**: Dependency resolution and installation are now handled by UV instead of Poetry within CI and potentially documentation builds.
- **Integration points impact**: Impacts CI/CD workflows (switching to UV-based reusable workflows), ReadTheDocs build process, and the semantic-release process.
- **Dependency changes and implications**: Project metadata and dependency specifications moved from Poetry-specific tables (`[tool.poetry]`, `[tool.poetry.dependencies]`, `[tool.poetry.group.*]`) to standard PEP 621 tables (`[project]`, `[project.optional-dependencies]`). Pre-commit hook versions updated.

## 2. Critical Findings
### 2.1 Must Fix (P0🔴)
**Issue**: CI Pipeline Failure in Lint Job
- **Analysis Confidence**: High
- **Impact**: The entire CI/CD pipeline is blocked as the `lint / ⚡ Lint (UV)` job is failing. This prevents automated checks, testing, building, and releasing. Merging this PR in its current state would leave the main branch with a broken CI.
- **Resolution**: Investigate the failure in the `mraniki/coding_toolset/.github/workflows/UVLint.yml@main` reusable workflow. Ensure UV is correctly installed and configured within the workflow environment, and that it's compatible with the project's linting setup (Ruff). Address any errors reported in the CI logs.

**Issue**: Broken Semantic Release Configuration
- **Analysis Confidence**: High
- **Impact**: Automated version bumping and package releases via `python-semantic-release` will fail because the command to build the package distribution is missing after removing Poetry.
- **Resolution**: Add the appropriate build command back to the `[tool.semantic_release]` section in `pyproject.toml`. A standard command using `build` would be: `build_command = "pip install build && python -m build"`.

### 2.2 Should Fix (P1🟡)
**Issue**: Hardcoded Project Version
- **Analysis Confidence**: High
- **Impact**: The project version (`version = "12.0.1"`) is hardcoded in `pyproject.toml`. This conflicts with the use of `python-semantic-release`, which is designed to automatically determine and update the version based on commit messages. The hardcoded version will prevent `semantic-release` from working correctly.
- **Suggested Solution**: Replace the hardcoded version with a placeholder like `version = "0.0.0"` in `pyproject.toml` under the `[project]` table. `semantic-release` will then manage this value during the release process.

**Issue**: Potentially Redundant Setuptools Package Configuration
- **Analysis Confidence**: Medium
- **Impact**: Explicitly listing `packages = ["dxsp"]` in `[tool.setuptools]` might be redundant if the package structure is standard and auto-discovery works as expected. While not harmful, it adds slight verbosity.
- **Suggested Solution**: Consider removing the `packages = ["dxsp"]` line from `[tool.setuptools]` and rely on Setuptools' auto-discovery. Verify that the build process still correctly identifies and includes the `dxsp` package. Adding `include-package-data = true` might be beneficial if non-code files need inclusion.

### 2.3 Consider (P2🟢)
**Area**: Dependency Version Specifiers
- **Analysis Confidence**: Medium
- **Improvement Opportunity**: Some dependencies use exact versions (e.g., `web3==6.20.4`) while others use ranges (`requests>=2.31.0`). While exact pinning ensures reproducibility, using compatible release specifiers (e.g., `web3>=6.20.4,<7.0.0`) allows for non-breaking updates. Consider standardizing the approach based on project needs for stability vs. receiving updates. Using a lock file mechanism (like `uv pip freeze > requirements.txt` and `uv pip sync requirements.txt`) could provide reproducibility alongside flexible specifiers in `pyproject.toml`.

**Area**: Documentation Updates
- **Analysis Confidence**: High
- **Improvement Opportunity**: The migration from Poetry to Setuptools/UV changes how developers might install the project locally for development or how users install it. The project's `README.md` and any contributor guidelines should be updated to reflect the new installation commands (e.g., using `pip install -e .[dev,test]` or `uv pip install -e .[dev,test]`).

**Area**: Commented-out Code in Pre-commit Config
- **Analysis Confidence**: High
- **Improvement Opportunity**: The `.github/.pre-commit-config.yaml` file contains a large commented-out block with older hook versions. This adds noise and can be confusing. Remove the commented-out section if it's no longer needed for reference.

### 2.4 Summary of Action Items
- **P0**: Fix CI lint job failure and add the missing `build_command` to `semantic-release` config. (Immediate)
- **P1**: Set project version to `0.0.0` for `semantic-release` and consider simplifying `[tool.setuptools]` config. (Before Merge)
- **P2**: Review dependency specifiers, update documentation for new setup, and clean up commented code in `.pre-commit-config.yaml`. (Post-Merge or Before Merge)

## 3. Technical Analysis
### 3.1 Code Logic Analysis
📁 **pyproject.toml** - **Project Configuration**
  - Submitted PR Code:
```toml
    [build-system]
    -requires = ["poetry-core"]
    -build-backend = "poetry.core.masonry.api"
    +requires = ["setuptools>=61.0"]
    +build-backend = "setuptools.build_meta"

    -[tool.poetry]
    +
    +[project]
     name = "dxsp"
    -version = "12.0.1"
    +version = "12.0.1" # P1: Should be "0.0.0" for semantic-release
     description = "DXSP (DeX SwaP), A defi swap helper package. Swap made easy."
    -authors = ["mraniki <8766259+mraniki@users.noreply.github.com>"]
    -license = "MIT License"
    +authors = [
    +  { name = "mraniki", email = "8766259+mraniki@users.noreply.github.com" },
    +]
    +license = "MIT"
     readme = "README.md"
    -packages = [
    -    {include = "dxsp"}
    -]
    +keywords = ["dex", "swap", "crypto", "exchange", "defi"]
    +requires-python = ">=3.10"
    +classifiers = [
    +    "Programming Language :: Python :: 3",
    +    "Programming Language :: Python :: 3.10",
    +    "Programming Language :: Python :: 3.11",
    +    "Programming Language :: Python :: 3.12",
    +]
    +dependencies = [
    +  "dynaconf>=3.2.0",
    +  "loguru>=0.6.0",
    +  "pycoingecko==3.2.0",
    +  "requests>=2.31.0",
    +  "web3==6.20.4", # P2: Consider pinning style
    +  "uniswap-python==0.7.1",
    +  "kwenta==1.2.0",
    +]

    -[tool.poetry.urls]
    +[project.urls]
     "Homepage" = "https://github.com/mraniki/dxsp"
     "Changelog" =  "https://github.com/mraniki/dxsp/blob/dev/CHANGELOG.rst"
     "Support" =  "https://github.com/mraniki/dxsp/discussions"
     "Issues" =  "https://github.com/mraniki/dxsp/issues"

    -[tool.poetry.dependencies]
    -python = "^3.10"
    -dynaconf = ">=3.2.0"
    -loguru = ">=0.6.0"
    -pycoingecko = "3.2.0"
    -requests = "^2.31.0"
    -web3 = "6.20.4"
    -uniswap-python = "0.7.1"
    -kwenta = "1.2.0"

    +[project.optional-dependencies]
    +dev = [
    +  "python-semantic-release>=8.0.8",
    +  "ruff~=0.11",
    +  "pre-commit~=4.0",
    +]
    +test = [
    +  "pytest~=8.2",
    +  "pytest-cov~=6.0",
    +  "pytest-asyncio~=0.25",
    +  "pytest-mock~=3.11",
    +  "pytest-loguru~=0.4",
    +  "eth_tester~=0.9.0b2",
    +]
    +docs = [
    +  "sphinx==7.4.7",
    +  "pydata-sphinx-theme==0.16.1",
    +  "sphinx-hoverxref==1.4.2",
    +  "sphinx_copybutton==0.5.2",
    +  "myst_parser==4.0.1",
    +  "sphinx_design~=0.6",
    +]

    -[tool.poetry.group.dev.dependencies]
    -python-semantic-release = ">=8.0.8"
    -ruff = "^0.11.0"
    -pre-commit = "^4.0.0"
    +
    +[tool.setuptools]
    +packages = ["dxsp"] # P1: Consider removing if auto-discovery works

     [tool.semantic_release]
     tag_format = "v{version}"
     commit_parser = "emoji"
    -build_command = "pip install poetry && poetry build" # P0: Missing build command
     version_variables = [
         "dxsp/__init__.py:__version__",
     ]
     version_toml = [
    -   "pyproject.toml:tool.poetry.version",
    +   "pyproject.toml:project.version",
     ]
```
  - Analysis:
    * The migration correctly adopts the PEP 621 standard for metadata and dependencies, moving configuration from `[tool.poetry.*]` to `[project]` and `[project.optional-dependencies]`.
    * The build system is correctly switched to Setuptools.
    * **Issue**: The `[tool.semantic_release]` section is missing the `build_command` necessary for Setuptools/build. (P0🔴)
    * **Issue**: The `project.version` is hardcoded, conflicting with `semantic-release`. (P1🟡)
    * **Potential Issue**: Dependency specifiers mix exact pinning and ranges; consider consistency and reproducibility strategy (e.g., lock file). (P2🟢)
    * **Minor Issue**: Explicit `packages` in `[tool.setuptools]` might be redundant. (P1🟡)
  - LlamaPReview Suggested Improvements:
```toml
    [build-system]
    requires = ["setuptools>=61.0"]
    build-backend = "setuptools.build_meta"

    [project]
    name = "dxsp"
    version = "0.0.0" # Use placeholder for semantic-release
    # ... other project metadata ...
    dependencies = [
      "dynaconf>=3.2.0,<4", # Example: Use compatible ranges
      "loguru>=0.6.0,<1",
      "pycoingecko==3.2.0", # Keep exact if necessary
      "requests>=2.31.0,<3",
      "web3>=6.20.4,<7", # Example: Use compatible ranges
      "uniswap-python==0.7.1", # Keep exact if necessary
      "kwenta==1.2.0", # Keep exact if necessary
    ]
    # ... optional-dependencies ...

    [tool.setuptools]
    # packages = ["dxsp"] # Remove if auto-discovery works
    include-package-data = true # Consider adding if needed

    [tool.semantic_release]
    tag_format = "v{version}"
    commit_parser = "emoji"
    build_command = "pip install build && python -m build" # Add build command
    version_variables = [
        "dxsp/__init__.py:__version__",
    ]
    version_toml = [
       "pyproject.toml:project.version",
    ]
    # ... rest of semantic_release config ...
```
  - Improvement rationale:
    * Technical benefits: Ensures `semantic-release` functions correctly, potentially simplifies configuration, clarifies dependency update strategy.
    * Business value: Enables automated releases, improves maintainability.
    * Risk assessment: Low risk for suggested changes; requires verification that auto-discovery works if `packages` is removed. Fixing build command and version is critical.

📁 **.github/workflows/👷Flow.yml** - **CI Workflow**
  - Submitted PR Code:
```diff
    ---
    +++
    @@ -1,58 +1,30 @@
     name: 👷Flow

     on:
       push:
    -    branches: ["main", "dev"]
    +    branches: ["main"] # Note: Trigger only on main now
       pull_request:
       workflow_dispatch:
       merge_group:
    -
    +
     jobs:
    -  # lint:
    -  #   uses: mraniki/coding_toolset/.github/workflows/🦺Lint.yml@main
    -  #   secrets: inherit
    -
    -  lint_direct:
    -    name: 🦺 Lint Direct (dxsp specific)
    -    runs-on: ubuntu-latest
    -    steps:
    -      - name: 📥 Checkout Code
    -        uses: actions/checkout@v4
    -        with:
    -          # Check out the specific commit associated with the PR head or push
    -          ref: ${{ github.event.pull_request.head.sha || github.sha }}
    -          fetch-depth: 0 # Fetch full history
    -
    -      - name: 🐍 Set up Python
    -        id: setup-python
    -        uses: actions/setup-python@v5
    -        with:
    -          python-version: "3.11"
    -
    -      - name: 🛠️ Install Ruff
    -        run: |
    -          pip install ruff # Install latest or specific version if needed
    -
    -      - name: 🚨 Lint Specific File
    -        run: |
    -          echo "Linting specific file: ./dxsp/handler/_client.py"
    -          ruff check --fix --no-cache ./dxsp/handler/_client.py
    -          echo "Linting specific file finished."
    -          # Optionally, add 'ruff check --fix --no-cache .' here if the specific file passes
    -          # to check the rest of the project.
    -
    +  lint:
    +    # uses: mraniki/coding_toolset/.github/workflows/🦺Lint.yml@main
    +    uses: mraniki/coding_toolset/.github/workflows/UVLint.yml@main # Using UV Lint workflow
    +    secrets: inherit
       test:
    -    # needs: [lint]
    -    needs: [lint_direct]
    -    uses: mraniki/coding_toolset/.github/workflows/🧪Test.yml@main
    +    needs: [lint]
    +    # uses: mraniki/coding_toolset/.github/workflows/🧪Test.yml@main
    +    uses: mraniki/coding_toolset/.github/workflows/UVTest.yml@main # Using UV Test workflow
         secrets: inherit
       build:
    -    # needs: [lint]
    -    needs: [lint_direct]
    -    uses: mraniki/coding_toolset/.github/workflows/🐍Build.yml@main
    +    needs: [lint]
    +    # uses: mraniki/coding_toolset/.github/workflows/🐍Build.yml@main
    +    uses: mraniki/coding_toolset/.github/workflows/UVBuild.yml@main # Using UV Build workflow
         secrets: inherit
       release:
         needs: [build, test]
    -    uses: mraniki/coding_toolset/.github/workflows/📦Release.yml@main
    +    # uses: mraniki/coding_toolset/.github/workflows/📦Release.yml@main
    +    uses: mraniki/coding_toolset/.github/workflows/UVRelease.yml@main # Use UV-specific release workflow
         secrets: inherit
```
  - Analysis:
    * The workflow correctly switches from previous linting/testing/building steps (some commented out, some direct) to using reusable workflows presumably based on UV (`UVLint.yml`, `UVTest.yml`, `UVBuild.yml`, `UVRelease.yml`).
    * **Issue**: The `lint` job using `UVLint.yml` is currently failing according to CI results. This indicates a problem either with the reusable workflow itself or its compatibility/configuration within this repository's context. (P0🔴)
    * The trigger branch for push events was changed from `["main", "dev"]` to `["main"]`. This is a workflow logic change to consider if pushes to `dev` should also trigger CI.
  - LlamaPReview Suggested Improvements: [No code change suggestion here, as the issue lies within the reusable workflow or its usage, requiring investigation based on CI logs.]
  - Improvement rationale: Fixing the CI failure is critical for validating PRs and ensuring code quality.

📁 **docs/.readthedocs.yaml** - **ReadTheDocs Configuration**
  - Submitted PR Code:
```diff
    ---
    +++
    @@ -1,24 +1,18 @@
     version: 2

     build:
       os: "ubuntu-22.04"
       tools:
         python: "3.11"
    +    # uv is not directly supported in tools, install via pip below
    +    # uv: "latest"
       jobs:
    -    # post_create_environment:
    -    #   # Install poetry
    -    #   # https://python-poetry.org/docs/#installing-manually
    -    #   - pip install poetry
    -    #   # Tell poetry to not use a virtual environment
    -    #   - poetry config virtualenvs.create false
    -    # post_install:
    -    #   # Install dependencies with 'docs' dependency group
    -    #   # https://python-poetry.org/docs/managing-dependencies/#dependency-groups
    -    #   - poetry install --with docs
    +    # Install uv after the environment is created
         post_create_environment:
    -      - python -m pip install poetry
    +      - pip install uv
         post_install:
    -      - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH python -m poetry install --with docs
    +      # Install the package with docs extras using uv
    +      - uv pip install -e '.[docs]'

     sphinx:
       configuration: docs/conf.py
```
  - Analysis:
    * The configuration correctly removes Poetry installation steps and adds steps to install `uv` via `pip`.
    * It then uses `uv pip install -e '.[docs]'` to install the project and its documentation dependencies, which aligns with the migration to Setuptools/UV and PEP 621 optional dependencies.
    * **Potential Risk**: ReadTheDocs doesn't officially list `uv` in its `build.tools`. While installing it via `pip` often works, there might be subtle environment incompatibilities or future breakages if ReadTheDocs changes its environment setup. Monitor documentation builds closely after merging.
  - LlamaPReview Suggested Improvements: [No code change suggestion, the approach is reasonable given RTD limitations. Consider adding a comment noting the unofficial UV support.]
  - Improvement rationale: N/A

📁 **.github/.pre-commit-config.yaml** - **Pre-commit Hooks**
  - Submitted PR Code:
```diff
    ---
    +++
    @@ -1,18 +1,38 @@
    +# repos:
    +#   - repo: https://github.com/astral-sh/ruff-pre-commit
    +#     rev: v0.0.292
    +#     hooks:
    +#      - id: ruff
    +#        args: [--fix, --exit-non-zero-on-fix]
    +
    +#   - repo: https://github.com/pre-commit/pre-commit-hooks
    +#     rev: v4.4.0
    +#     hooks:
    +#       - id: check-merge-conflict
    +#       - id: trailing-whitespace
    +
    +#   - repo: https://github.com/PyCQA/bandit
    +#     rev: '1.7.5'
    +#     hooks:
    +#     - id: bandit
    +#       args: ["-c", "pyproject.toml"]
    +
    + # P2: Consider removing this commented block
     repos:
       - repo: https://github.com/astral-sh/ruff-pre-commit
    -    rev: v0.0.292
    +    rev: v0.11.7 # Updated Ruff
         hooks:
          - id: ruff
            args: [--fix, --exit-non-zero-on-fix]

       - repo: https://github.com/pre-commit/pre-commit-hooks
    -    rev: v4.4.0
    +    rev: v5.0.0 # Updated pre-commit-hooks
         hooks:
           - id: check-merge-conflict
           - id: trailing-whitespace

       - repo: https://github.com/PyCQA/bandit
    -    rev: '1.7.5'
    +    rev: '1.8.3' # Updated Bandit
         hooks:
         - id: bandit
           args: ["-c", "pyproject.toml"]
```
  - Analysis:
    * The versions for `ruff-pre-commit`, `pre-commit-hooks`, and `bandit` have been updated to newer revisions. This is generally good practice for incorporating bug fixes and new features from these tools.
    * **Minor Issue**: There's a large commented-out section containing the previous configuration, which adds clutter. (P2🟢)
  - LlamaPReview Suggested Improvements:
```yaml
    # Remove the commented-out block above this line

    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.11.7
        hooks:
         - id: ruff
           args: [--fix, --exit-non-zero-on-fix]

      - repo: https://github.com/pre-commit/pre-commit-hooks
        rev: v5.0.0
        hooks:
          - id: check-merge-conflict
          - id: trailing-whitespace

      - repo: https://github.com/PyCQA/bandit
        rev: '1.8.3'
        hooks:
        - id: bandit
          args: ["-c", "pyproject.toml"]
```
  - Improvement rationale: Improves readability and maintainability of the configuration file.

### 3.2 Key Quality Aspects 
- **Testing strategy and coverage**: The CI pipeline includes a `test` job using `UVTest.yml`. However, this job is currently skipped due to the failure in the preceding `lint` job. The effectiveness of the testing strategy under the new UV/Setuptools setup cannot be verified until the CI pipeline is fixed.
- **Documentation needs**: As noted in P2, contributor and user documentation needs updating to reflect the new build/install procedures.
- **Dependency Management**: The move to PEP 621 is positive for standardization. However, the strategy for version pinning (exact vs. ranges) and the lack of a committed lock file (common practice with tools like Poetry or pip-tools/uv) introduce potential risks for dependency conflicts or unexpected behavior if transitive dependencies change. (P2🟢)

## 4. Overall Evaluation
- **Technical assessment**: The migration to Setuptools and PEP 621 is a positive step towards standardization. The introduction of UV has the potential for performance benefits but introduces risks, as evidenced by the current CI failure and its less established support in tools like ReadTheDocs compared to pip/Poetry. The core changes in `pyproject.toml` are mostly correct but require critical fixes for release automation and version handling.
- **Business impact**: This is a significant breaking change for contributors and potentially users if they rely on specific Poetry workflows. The benefits of standardization and potential speed improvements need to be weighed against the migration cost and the risks associated with the new tooling (especially the CI failure).
- **Risk evaluation**: High risk currently due to the broken CI pipeline and the incorrect semantic-release configuration. Medium risk associated with dependency management strategy (version specifiers) and potential UV compatibility issues (e.g., in ReadTheDocs).
- **Notable positive aspects and good practices**: Adoption of PEP 621 standard, updating pre-commit hook dependencies.
- **Implementation quality**: Requires critical fixes (P0) and important improvements (P1) before merging. The core migration logic is present but incomplete/incorrect in key areas related to automation and CI.
- **Final recommendation**: **Request Changes**

The PR introduces necessary modernization but contains critical blocking issues (CI failure, broken release process) and important configuration errors (hardcoded version) that must be addressed before it can be merged. Further consideration should be given to dependency pinning strategy and documentation updates.

---
💡 **LlamaPReview Community**
Have feedback on this AI Code review tool? Join our [GitHub Discussions](https://github.com/JetXu-LLM/LlamaPReview-site/discussions) to share your thoughts and help shape the future of LlamaPReview.
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-27T13:56:40Z
user: login: code-review-doctor[bot]
body: Some food for thought. View full project report [here](http://codereview.doctor/mraniki/dxsp).
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T18:00:18Z

Review comments:
diff_hunk: @@ -5,18 +5,63 @@
 
 from dynaconf import Dynaconf
 
+# Define the root path of the project library
 ROOT = os.path.dirname(__file__)
+# Define the library's internal default settings path
+DEFAULT_SETTINGS_PATH = os.path.join(ROOT, "default_settings.toml")
 
+# --- Start Modification ---
+# Check for the application's config directory via environment variable
+# e.g., /app or ./ if run locally from tt root
+APP_CONFIG_DIR = os.environ.get("TT_CONFIG_DIR")
+
+# Construct paths to app/user config files if the directory is specified
+talky_settings_path = None
+user_settings_path = None
+secrets_path = None
+op_path = None
+
+if APP_CONFIG_DIR and os.path.isdir(APP_CONFIG_DIR):
+    print(f"DXSP Config: Found TT_CONFIG_DIR: {APP_CONFIG_DIR}")
+    # Use the specific name tt expects for its default settings
+    talky_settings_path = os.path.join(APP_CONFIG_DIR, "tt", "talky_settings.toml") 
+    user_settings_path = os.path.join(APP_CONFIG_DIR, "settings.toml")
+    secrets_path = os.path.join(APP_CONFIG_DIR, ".secrets.toml")
+    op_path = os.path.join(APP_CONFIG_DIR, ".op.toml") # If this lib also reads this
+else:
+    print(
+        f"DXSP Config: TT_CONFIG_DIR not set or invalid "
path: dxsp/config.py
user: login: code-review-doctor[bot]
body: ```suggestion
        "DXSP Config: TT_CONFIG_DIR not set or invalid "
```

f-string is unnecessary here. This can just be a string. [Read more](https://codereview.doctor/advice/NoRedundantFString).
created_at: 2025-04-27T18:00:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,18 +5,63 @@
 
 from dynaconf import Dynaconf
 
+# Define the root path of the project library
 ROOT = os.path.dirname(__file__)
+# Define the library's internal default settings path
+DEFAULT_SETTINGS_PATH = os.path.join(ROOT, "default_settings.toml")
 
+# --- Start Modification ---
+# Check for the application's config directory via environment variable
+# e.g., /app or ./ if run locally from tt root
+APP_CONFIG_DIR = os.environ.get("TT_CONFIG_DIR")
+
+# Construct paths to app/user config files if the directory is specified
+talky_settings_path = None
+user_settings_path = None
+secrets_path = None
+op_path = None
+
+if APP_CONFIG_DIR and os.path.isdir(APP_CONFIG_DIR):
+    print(f"DXSP Config: Found TT_CONFIG_DIR: {APP_CONFIG_DIR}")
+    # Use the specific name tt expects for its default settings
+    talky_settings_path = os.path.join(APP_CONFIG_DIR, "tt", "talky_settings.toml") 
+    user_settings_path = os.path.join(APP_CONFIG_DIR, "settings.toml")
+    secrets_path = os.path.join(APP_CONFIG_DIR, ".secrets.toml")
+    op_path = os.path.join(APP_CONFIG_DIR, ".op.toml") # If this lib also reads this
+else:
+    print(
+        f"DXSP Config: TT_CONFIG_DIR not set or invalid "
+        f"('{APP_CONFIG_DIR}'). Relying on library defaults."
+    )
+
+# Build the settings_files list dynamically
+# Order: Library Default < TT Default < User Settings < User Secrets
+settings_files = []
+if os.path.exists(DEFAULT_SETTINGS_PATH):
+    settings_files.append(DEFAULT_SETTINGS_PATH)
+else:
+    print(
+        f"DXSP Config: Warning - Library default not found "
path: dxsp/config.py
user: login: code-review-doctor[bot]
body: ```suggestion
        "DXSP Config: Warning - Library default not found "
```

Similarly, f-string is unnecessary here.
created_at: 2025-04-27T18:00:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-04-27T13:52:22Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request migrates the project's build and dependency management system from Poetry to Setuptools and uv. This involves updating the pyproject.toml to use PEP 621 standards, configuring Setuptools, updating tooling versions in pre-commit hooks, and adapting the ReadTheDocs build process to use uv.

_No diagrams generated as the changes look simple and do not need a visual representation._

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Migrated project build and dependency configuration from Poetry to Setuptools and PEP 621. | <ul><li>Changed the build backend from poetry-core to setuptools.</li><li>Migrated project metadata from [tool.poetry] to the standard [project] table.</li><li>Moved core dependencies to [project.dependencies].</li><li>Moved dependency groups (dev, test, docs) to [project.optional-dependencies].</li><li>Added setuptools configuration for package discovery.</li><li>Updated semantic release configuration to point to the new version location.</li></ul> | `pyproject.toml` |
| Adopted uv for dependency installation and build processes. | <ul><li>Modified ReadTheDocs configuration to install uv.</li><li>Updated ReadTheDocs build steps to use uv pip install -e '.[docs]'.</li><li>Updated workflow configuration to use uv (workflow file content not shown in diff).</li></ul> | `docs/.readthedocs.yaml`<br/>`.github/workflows/✨Flow.yml` |
| Updated versions of pre-commit hooks. | <ul><li>Updated Ruff pre-commit hook version.</li><li>Updated pre-commit-hooks version.</li><li>Updated Bandit pre-commit hook version.</li></ul> | `.github/.pre-commit-config.yaml` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-04-30T05:27:58Z
body: ## [Codecov](https://app.codecov.io/gh/mraniki/dxsp/pull/783?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki) Report
Attention: Patch coverage is `54.63918%` with `44 lines` in your changes missing coverage. Please review.
> Project coverage is 63.72%. Comparing base [(`2eecc19`)](https://app.codecov.io/gh/mraniki/dxsp/commit/2eecc19d32713e38928d79d47a737d8fcbee617b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki) to head [(`1a52706`)](https://app.codecov.io/gh/mraniki/dxsp/commit/1a52706a55c721e97393e5f89c91a00f3158c511?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki).
> Report is 14 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/mraniki/dxsp/pull/783?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki) | Patch % | Lines |
|---|---|---|
| [dxsp/handler/\_client.py](https://app.codecov.io/gh/mraniki/dxsp/pull/783?src=pr&el=tree&filepath=dxsp%2Fhandler%2F_client.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki#diff-ZHhzcC9oYW5kbGVyL19jbGllbnQucHk=) | 54.34% | [42 Missing :warning: ](https://app.codecov.io/gh/mraniki/dxsp/pull/783?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki) |
| [dxsp/handler/zerox.py](https://app.codecov.io/gh/mraniki/dxsp/pull/783?src=pr&el=tree&filepath=dxsp%2Fhandler%2Fzerox.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki#diff-ZHhzcC9oYW5kbGVyL3plcm94LnB5) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/mraniki/dxsp/pull/783?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki) |
| [dxsp/main.py](https://app.codecov.io/gh/mraniki/dxsp/pull/783?src=pr&el=tree&filepath=dxsp%2Fmain.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki#diff-ZHhzcC9tYWluLnB5) | 75.00% | [1 Missing :warning: ](https://app.codecov.io/gh/mraniki/dxsp/pull/783?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #783      +/-   ##
==========================================
- Coverage   67.54%   63.72%   -3.82%     
==========================================
  Files          13       13              
  Lines         801      827      +26     
==========================================
- Hits          541      527      -14     
- Misses        260      300      +40     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mraniki/dxsp/pull/783?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mraniki).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE