From a409433759df55c74960600e64d77f7787c37ee8 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 4 Oct 2024 10:59:02 +0900
Subject: [PATCH 01/43] Migrate to Pydantic v2, update model validation and fix
 async issues

- Migrated to Pydantic v2:
  - Replaced deprecated `parse_obj()` and `parse_raw()` with
`model_validate()` and `model_validate_json()`.
  - Replaced `.dict()` with `.model_dump()` for serializing models to dictionaries.
  - Updated `validator` to `field_validator` and `root_validator` to
`model_validator` to comply with Pydantic v2 syntax changes.

- Fixed asyncio issues:
  - Added `await` for asynchronous methods like `raise_for_status()`
in `RemoteAccount` and other HTTP operations to avoid `RuntimeWarning`.

- Updated config handling:
  - Used `ClassVar` for constants in `Settings` and other configuration classes.
  - Replaced `Config` with `ConfigDict` in Pydantic models
to follow v2 conventions.
  - Added default values for missing fields in chain configurations
(`CHAINS_SEPOLIA_ACTIVE`, etc.).

- Adjusted signature handling:
  - Updated the signing logic to prepend `0x` in the `BaseAccount` signature
generation to ensure correct Ethereum address formatting.

- Minor fixes:
  - Resolved issue with extra fields not being allowed by default
by specifying `extra="allow"` or `extra="forbid"` where necessary.
  - Fixed tests to account for changes in model validation and
serialization behavior.
  - Added `pydantic-settings` as a new dependency for configuration management.
---
 pyproject.toml                                |  1 +
 src/aleph/sdk/chains/common.py                |  2 +-
 src/aleph/sdk/chains/remote.py                |  4 +-
 src/aleph/sdk/client/authenticated_http.py    | 18 +++---
 src/aleph/sdk/client/http.py                  |  2 +-
 .../sdk/client/vm_confidential_client.py      |  2 +-
 src/aleph/sdk/conf.py                         | 62 ++++++++++---------
 src/aleph/sdk/query/responses.py              | 10 +--
 src/aleph/sdk/utils.py                        |  8 ++-
 tests/unit/aleph_vm_authentication.py         | 40 ++++++------
 tests/unit/conftest.py                        |  6 +-
 tests/unit/test_remote_account.py             |  2 +-
 tests/unit/test_utils.py                      | 23 +++----
 tests/unit/test_vm_client.py                  |  4 +-
 14 files changed, 95 insertions(+), 89 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index f533bfe2..8a7bbc09 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,6 +29,7 @@ dependencies = [
     "eth_abi>=4.0.0; python_version>=\"3.11\"",
     "jwcrypto==1.5.6",
     "python-magic",
+    "pydantic-settings",
     "typing_extensions",
     "aioresponses>=0.7.6",
     "aleph-superfluid>=0.2.1",
diff --git a/src/aleph/sdk/chains/common.py b/src/aleph/sdk/chains/common.py
index 0a90183c..8f57f9b4 100644
--- a/src/aleph/sdk/chains/common.py
+++ b/src/aleph/sdk/chains/common.py
@@ -72,7 +72,7 @@ async def sign_message(self, message: Dict) -> Dict:
         """
         message = self._setup_sender(message)
         signature = await self.sign_raw(get_verification_buffer(message))
-        message["signature"] = signature.hex()
+        message["signature"] = "0x" + signature.hex()
         return message
 
     @abstractmethod
diff --git a/src/aleph/sdk/chains/remote.py b/src/aleph/sdk/chains/remote.py
index 931b68f3..917cf39b 100644
--- a/src/aleph/sdk/chains/remote.py
+++ b/src/aleph/sdk/chains/remote.py
@@ -52,7 +52,7 @@ async def from_crypto_host(
             session = aiohttp.ClientSession(connector=connector)
 
         async with session.get(f"{host}/properties") as response:
-            response.raise_for_status()
+            await response.raise_for_status()
             data = await response.json()
             properties = AccountProperties(**data)
 
@@ -75,7 +75,7 @@ def private_key(self):
     async def sign_message(self, message: Dict) -> Dict:
         """Sign a message inplace."""
         async with self._session.post(f"{self._host}/sign", json=message) as response:
-            response.raise_for_status()
+            await response.raise_for_status()
             return await response.json()
 
     async def sign_raw(self, buffer: bytes) -> bytes:
diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py
index f84b97ca..2f30e534 100644
--- a/src/aleph/sdk/client/authenticated_http.py
+++ b/src/aleph/sdk/client/authenticated_http.py
@@ -259,7 +259,7 @@ async def _broadcast(
         url = "/api/v0/messages"
         logger.debug(f"Posting message on {url}")
 
-        message_dict = message.dict(include=self.BROADCAST_MESSAGE_FIELDS)
+        message_dict = message.model_dump(include=self.BROADCAST_MESSAGE_FIELDS)
         async with self.http_session.post(
             url,
             json={
@@ -301,7 +301,7 @@ async def create_post(
         )
 
         message, status, _ = await self.submit(
-            content=content.dict(exclude_none=True),
+            content=content.model_dump(exclude_none=True),
             message_type=MessageType.post,
             channel=channel,
             allow_inlining=inline,
@@ -329,7 +329,7 @@ async def create_aggregate(
         )
 
         message, status, _ = await self.submit(
-            content=content_.dict(exclude_none=True),
+            content=content_.model_dump(exclude_none=True),
             message_type=MessageType.aggregate,
             channel=channel,
             allow_inlining=inline,
@@ -403,7 +403,7 @@ async def create_store(
         content = StoreContent(**values)
 
         message, status, _ = await self.submit(
-            content=content.dict(exclude_none=True),
+            content=content.model_dump(exclude_none=True),
             message_type=MessageType.store,
             channel=channel,
             allow_inlining=True,
@@ -496,7 +496,7 @@ async def create_program(
         assert content.on.persistent == persistent
 
         message, status, _ = await self.submit(
-            content=content.dict(exclude_none=True),
+            content=content.model_dump(exclude_none=True),
             message_type=MessageType.program,
             channel=channel,
             storage_engine=storage_engine,
@@ -572,7 +572,7 @@ async def create_instance(
             payment=payment,
         )
         message, status, response = await self.submit(
-            content=content.dict(exclude_none=True),
+            content=content.model_dump(exclude_none=True),
             message_type=MessageType.instance,
             channel=channel,
             storage_engine=storage_engine,
@@ -618,7 +618,7 @@ async def forget(
         )
 
         message, status, _ = await self.submit(
-            content=content.dict(exclude_none=True),
+            content=content.model_dump(exclude_none=True),
             message_type=MessageType.forget,
             channel=channel,
             storage_engine=storage_engine,
@@ -662,11 +662,11 @@ async def _storage_push_file_with_message(
         # Prepare the STORE message
         message = await self.generate_signed_message(
             message_type=MessageType.store,
-            content=store_content.dict(exclude_none=True),
+            content=store_content.model_dump(exclude_none=True),
             channel=channel,
         )
         metadata = {
-            "message": message.dict(exclude_none=True),
+            "message": message.model_dump(exclude_none=True),
             "sync": sync,
         }
         data.add_field(
diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py
index 2c953d4e..60e65bb2 100644
--- a/src/aleph/sdk/client/http.py
+++ b/src/aleph/sdk/client/http.py
@@ -180,7 +180,7 @@ async def get_posts(
             posts: List[Post] = []
             for post_raw in posts_raw:
                 try:
-                    posts.append(Post.parse_obj(post_raw))
+                    posts.append(Post.model_validate(post_raw))
                 except ValidationError as e:
                     if not ignore_invalid_messages:
                         raise e
diff --git a/src/aleph/sdk/client/vm_confidential_client.py b/src/aleph/sdk/client/vm_confidential_client.py
index e027b384..0d9d6e18 100644
--- a/src/aleph/sdk/client/vm_confidential_client.py
+++ b/src/aleph/sdk/client/vm_confidential_client.py
@@ -105,7 +105,7 @@ async def measurement(self, vm_id: ItemHash) -> SEVMeasurement:
         status, text = await self.perform_operation(
             vm_id, "confidential/measurement", method="GET"
         )
-        sev_measurement = SEVMeasurement.parse_raw(text)
+        sev_measurement = SEVMeasurement.model_validate_json(text)
         return sev_measurement
 
     async def validate_measure(
diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 114652b7..e9a46293 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -3,11 +3,13 @@
 import os
 from pathlib import Path
 from shutil import which
-from typing import Dict, Optional, Union
+from typing import ClassVar, Dict, Optional, Union
+
+from pydantic_settings import BaseSettings
 
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
-from pydantic import BaseModel, BaseSettings, Field
+from pydantic import BaseModel, ConfigDict, Field
 
 from aleph.sdk.types import ChainInfo
 
@@ -41,7 +43,7 @@ class Settings(BaseSettings):
     REMOTE_CRYPTO_HOST: Optional[str] = None
     REMOTE_CRYPTO_UNIX_SOCKET: Optional[str] = None
     ADDRESS_TO_USE: Optional[str] = None
-    HTTP_REQUEST_TIMEOUT = 10.0
+    HTTP_REQUEST_TIMEOUT: ClassVar[float] = 10.0
 
     DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS"
     DEFAULT_RUNTIME_ID: str = (
@@ -83,12 +85,12 @@ class Settings(BaseSettings):
 
     CODE_USES_SQUASHFS: bool = which("mksquashfs") is not None  # True if command exists
 
-    VM_URL_PATH = "https://aleph.sh/vm/{hash}"
-    VM_URL_HOST = "https://{hash_base32}.aleph.sh"
+    VM_URL_PATH: ClassVar[str] = "https://aleph.sh/vm/{hash}"
+    VM_URL_HOST: ClassVar[str] = "https://{hash_base32}.aleph.sh"
 
     # Web3Provider settings
-    TOKEN_DECIMALS = 18
-    TX_TIMEOUT = 60 * 3
+    TOKEN_DECIMALS: ClassVar[int] = 18
+    TX_TIMEOUT: ClassVar[int] = 60 * 3
     CHAINS: Dict[Union[Chain, str], ChainInfo] = {
         # TESTNETS
         "SEPOLIA": ChainInfo(
@@ -124,28 +126,29 @@ class Settings(BaseSettings):
         ),
     }
     # Add all placeholders to allow easy dynamic setup of CHAINS
-    CHAINS_SEPOLIA_ACTIVE: Optional[bool]
-    CHAINS_ETH_ACTIVE: Optional[bool]
-    CHAINS_AVAX_ACTIVE: Optional[bool]
-    CHAINS_BASE_ACTIVE: Optional[bool]
-    CHAINS_BSC_ACTIVE: Optional[bool]
-    CHAINS_SEPOLIA_RPC: Optional[str]
-    CHAINS_ETH_RPC: Optional[str]
-    CHAINS_AVAX_RPC: Optional[str]
-    CHAINS_BASE_RPC: Optional[str]
-    CHAINS_BSC_RPC: Optional[str]
+    CHAINS_SEPOLIA_ACTIVE: Optional[bool] = None
+    CHAINS_ETH_ACTIVE: Optional[bool] = None
+    CHAINS_AVAX_ACTIVE: Optional[bool] = None
+    CHAINS_BASE_ACTIVE: Optional[bool] = None
+    CHAINS_BSC_ACTIVE: Optional[bool] = None
+    CHAINS_SEPOLIA_RPC: Optional[str] = None
+    CHAINS_ETH_RPC: Optional[str] = None
+    CHAINS_AVAX_RPC: Optional[str] = None
+    CHAINS_BASE_RPC: Optional[str] = None
+    CHAINS_BSC_RPC: Optional[str] = None
 
     # Dns resolver
-    DNS_IPFS_DOMAIN = "ipfs.public.aleph.sh"
-    DNS_PROGRAM_DOMAIN = "program.public.aleph.sh"
-    DNS_INSTANCE_DOMAIN = "instance.public.aleph.sh"
-    DNS_STATIC_DOMAIN = "static.public.aleph.sh"
-    DNS_RESOLVERS = ["9.9.9.9", "1.1.1.1"]
-
-    class Config:
-        env_prefix = "ALEPH_"
-        case_sensitive = False
-        env_file = ".env"
+    DNS_IPFS_DOMAIN: ClassVar[str] = "ipfs.public.aleph.sh"
+    DNS_PROGRAM_DOMAIN: ClassVar[str] = "program.public.aleph.sh"
+    DNS_INSTANCE_DOMAIN: ClassVar[str] = "instance.public.aleph.sh"
+    DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
+    DNS_RESOLVERS: ClassVar[str] = ["9.9.9.9", "1.1.1.1"]
+
+    model_config = ConfigDict(
+       env_prefix="ALEPH_",
+       case_sensitive=False,
+       env_file=".env"
+    )
 
 
 class MainConfiguration(BaseModel):
@@ -156,8 +159,7 @@ class MainConfiguration(BaseModel):
     path: Path
     chain: Chain
 
-    class Config:
-        use_enum_values = True
+    model_config = ConfigDict(use_enum_values = True)
 
 
 # Settings singleton
@@ -213,7 +215,7 @@ def save_main_configuration(file_path: Path, data: MainConfiguration):
     Synchronously save a single ChainAccount object as JSON to a file.
     """
     with file_path.open("w") as file:
-        data_serializable = data.dict()
+        data_serializable = data.model_dump()
         data_serializable["path"] = str(data_serializable["path"])
         json.dump(data_serializable, file, indent=4)
 
diff --git a/src/aleph/sdk/query/responses.py b/src/aleph/sdk/query/responses.py
index 4b598f12..277a1bea 100644
--- a/src/aleph/sdk/query/responses.py
+++ b/src/aleph/sdk/query/responses.py
@@ -9,7 +9,7 @@
     ItemType,
     MessageConfirmation,
 )
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, ConfigDict, Field
 
 
 class Post(BaseModel):
@@ -48,9 +48,9 @@ class Post(BaseModel):
     ref: Optional[Union[str, Any]] = Field(
         description="Other message referenced by this one"
     )
+    address: Optional[str] = Field(description="Address of the sender")
 
-    class Config:
-        allow_extra = False
+    model_config = ConfigDict(extra="forbid")
 
 
 class PaginationResponse(BaseModel):
@@ -64,14 +64,14 @@ class PostsResponse(PaginationResponse):
     """Response from an aleph.im node API on the path /api/v0/posts.json"""
 
     posts: List[Post]
-    pagination_item = "posts"
+    pagination_item: str = "posts"
 
 
 class MessagesResponse(PaginationResponse):
     """Response from an aleph.im node API on the path /api/v0/messages.json"""
 
     messages: List[AlephMessage]
-    pagination_item = "messages"
+    pagination_item: str = "messages"
 
 
 class PriceResponse(BaseModel):
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index 116c7b42..1cdb6608 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -28,13 +28,15 @@
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
 
+from pydantic import BaseModel
+
 from aleph_message.models import ItemHash, MessageType
 from aleph_message.models.execution.program import Encoding
 from aleph_message.models.execution.volume import MachineVolume
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
-from pydantic.json import pydantic_encoder
+import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
@@ -173,7 +175,7 @@ def extended_json_encoder(obj: Any) -> Any:
     elif isinstance(obj, time):
         return obj.hour * 3600 + obj.minute * 60 + obj.second + obj.microsecond / 1e6
     else:
-        return pydantic_encoder(obj)
+        return pydantic_core.to_jsonable_python(obj)
 
 
 def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
@@ -185,7 +187,7 @@ def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
         return volume_dict
     for volume_type in get_args(MachineVolume):
         try:
-            return volume_type.parse_obj(volume_dict)
+            return volume_type.model_validate(volume_dict)
         except ValueError:
             continue
     else:
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index 491da51a..86a4cd20 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -13,7 +13,7 @@
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 from jwcrypto.jwa import JWA
-from pydantic import BaseModel, ValidationError, root_validator, validator
+from pydantic import BaseModel, ValidationError, model_validator, field_validator
 
 from aleph.sdk.utils import bytes_from_hex
 
@@ -63,23 +63,23 @@ class SignedPubKeyHeader(BaseModel):
     signature: bytes
     payload: bytes
 
-    @validator("signature")
+    @field_validator("signature")
     def signature_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the signature from hexadecimal to bytes"""
 
         return bytes_from_hex(value.decode())
 
-    @validator("payload")
+    @field_validator("payload")
     def payload_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the payload from hexadecimal to bytes"""
 
         return bytes_from_hex(value.decode())
 
-    @root_validator(pre=False, skip_on_failure=True)
+    @model_validator(mode="after")
     def check_expiry(cls, values) -> Dict[str, bytes]:
         """Check that the token has not expired"""
-        payload: bytes = values["payload"]
-        content = SignedPubKeyPayload.parse_raw(payload)
+        payload: bytes = values.payload
+        content = SignedPubKeyPayload.model_validate_json(payload)
 
         if not is_token_still_valid(content.expires):
             msg = "Token expired"
@@ -87,12 +87,12 @@ def check_expiry(cls, values) -> Dict[str, bytes]:
 
         return values
 
-    @root_validator(pre=False, skip_on_failure=True)
+    @model_validator(mode="after")
     def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
         """Check that the signature is valid"""
-        signature: bytes = values["signature"]
-        payload: bytes = values["payload"]
-        content = SignedPubKeyPayload.parse_raw(payload)
+        signature: bytes = values.signature
+        payload: bytes = values.payload
+        content = SignedPubKeyPayload.model_validate_json(payload)
 
         if not verify_wallet_signature(signature, payload.hex(), content.address):
             msg = "Invalid signature"
@@ -103,7 +103,7 @@ def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
     @property
     def content(self) -> SignedPubKeyPayload:
         """Return the content of the header"""
-        return SignedPubKeyPayload.parse_raw(self.payload)
+        return SignedPubKeyPayload.model_validate_json(self.payload)
 
 
 class SignedOperationPayload(BaseModel):
@@ -113,7 +113,7 @@ class SignedOperationPayload(BaseModel):
     path: str
     # body_sha256: str  # disabled since there is no body
 
-    @validator("time")
+    @field_validator("time")
     def time_is_current(cls, v: datetime.datetime) -> datetime.datetime:
         """Check that the time is current and the payload is not a replay attack."""
         max_past = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(
@@ -135,7 +135,7 @@ class SignedOperation(BaseModel):
     signature: bytes
     payload: bytes
 
-    @validator("signature")
+    @field_validator("signature")
     def signature_must_be_hex(cls, value: str) -> bytes:
         """Convert the signature from hexadecimal to bytes"""
 
@@ -147,17 +147,17 @@ def signature_must_be_hex(cls, value: str) -> bytes:
             logger.warning(value)
             raise error
 
-    @validator("payload")
+    @field_validator("payload")
     def payload_must_be_hex(cls, v) -> bytes:
         """Convert the payload from hexadecimal to bytes"""
         v = bytes.fromhex(v.decode())
-        _ = SignedOperationPayload.parse_raw(v)
+        _ = SignedOperationPayload.model_validate_json(v)
         return v
 
     @property
     def content(self) -> SignedOperationPayload:
         """Return the content of the header"""
-        return SignedOperationPayload.parse_raw(self.payload)
+        return SignedOperationPayload.model_validate_json(self.payload)
 
 
 def get_signed_pubkey(request: web.Request) -> SignedPubKeyHeader:
@@ -168,7 +168,7 @@ def get_signed_pubkey(request: web.Request) -> SignedPubKeyHeader:
         raise web.HTTPBadRequest(reason="Missing X-SignedPubKey header")
 
     try:
-        return SignedPubKeyHeader.parse_raw(signed_pubkey_header)
+        return SignedPubKeyHeader.model_validate_json(signed_pubkey_header)
 
     except KeyError as error:
         logger.debug(f"Missing X-SignedPubKey header: {error}")
@@ -199,7 +199,7 @@ def get_signed_operation(request: web.Request) -> SignedOperation:
     """Get the signed operation public key that is signed by the ephemeral key from the request headers."""
     try:
         signed_operation = request.headers["X-SignedOperation"]
-        return SignedOperation.parse_raw(signed_operation)
+        return SignedOperation.model_validate_json(signed_operation)
     except KeyError as error:
         raise web.HTTPBadRequest(reason="Missing X-SignedOperation header") from error
     except json.JSONDecodeError as error:
@@ -259,8 +259,8 @@ async def authenticate_websocket_message(
     message, domain_name: Optional[str] = DOMAIN_NAME
 ) -> str:
     """Authenticate a websocket message since JS cannot configure headers on WebSockets."""
-    signed_pubkey = SignedPubKeyHeader.parse_obj(message["X-SignedPubKey"])
-    signed_operation = SignedOperation.parse_obj(message["X-SignedOperation"])
+    signed_pubkey = SignedPubKeyHeader.model_validate(message["X-SignedPubKey"])
+    signed_operation = SignedOperation.model_validate(message["X-SignedOperation"])
     if signed_operation.content.domain != domain_name:
         logger.debug(
             f"Invalid domain '{signed_pubkey.content.domain}' != '{domain_name}'"
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index c1c56fcd..bea0d886 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -71,7 +71,7 @@ def rejected_message():
 @pytest.fixture
 def aleph_messages() -> List[AlephMessage]:
     return [
-        AggregateMessage.parse_obj(
+        AggregateMessage.model_validate(
             {
                 "item_hash": "5b26d949fe05e38f535ef990a89da0473f9d700077cced228f2d36e73fca1fd6",
                 "type": "AGGREGATE",
@@ -95,7 +95,7 @@ def aleph_messages() -> List[AlephMessage]:
                 "confirmed": False,
             }
         ),
-        PostMessage.parse_obj(
+        PostMessage.model_validate(
             {
                 "item_hash": "70f3798fdc68ce0ee03715a5547ee24e2c3e259bf02e3f5d1e4bf5a6f6a5e99f",
                 "type": "POST",
@@ -135,7 +135,7 @@ def json_post() -> dict:
 def raw_messages_response(aleph_messages) -> Callable[[int], Dict[str, Any]]:
     return lambda page: {
         "messages": (
-            [message.dict() for message in aleph_messages] if int(page) == 1 else []
+            [message.model_dump() for message in aleph_messages] if int(page) == 1 else []
         ),
         "pagination_item": "messages",
         "pagination_page": int(page),
diff --git a/tests/unit/test_remote_account.py b/tests/unit/test_remote_account.py
index cb4a2af5..3abe979e 100644
--- a/tests/unit/test_remote_account.py
+++ b/tests/unit/test_remote_account.py
@@ -22,7 +22,7 @@ async def test_remote_storage():
                 curve="secp256k1",
                 address=local_account.get_address(),
                 public_key=local_account.get_public_key(),
-            ).dict()
+            ).model_dump()
         )
 
         remote_account = await RemoteAccount.from_crypto_host(
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index bfca23a5..df27ee2f 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -115,16 +115,17 @@ def test_enum_as_str():
         (
             MessageType.aggregate,
             {
-                "content": {
-                    "Hello": MachineResources(
-                        vcpus=1,
-                        memory=1024,
-                        seconds=1,
-                    )
+                'address': '0x1',
+                'content': {
+                    'Hello': {
+                        'vcpus': 1,
+                        'memory': 1024,
+                        'seconds': 1,
+                        'published_ports': None,
+                    },
                 },
-                "key": "test",
-                "address": "0x1",
-                "time": 1.0,
+                'key': 'test',
+                'time': 1.0,
             },
         ),
     ],
@@ -140,7 +141,7 @@ async def test_prepare_aleph_message(
             channel="TEST",
         )
 
-    assert message.content.dict() == content
+    assert message.content.model_dump() == content
 
 
 def test_parse_immutable_volume():
@@ -215,7 +216,7 @@ def test_compute_confidential_measure():
     assert base64.b64encode(tik) == b"npOTEc4mtRGfXfB+G6EBdw=="
     expected_hash = "d06471f485c0a61aba5a431ec136b947be56907acf6ed96afb11788ae4525aeb"
     nonce = base64.b64decode("URQNqJAqh/2ep4drjx/XvA==")
-    sev_info = SEVInfo.parse_obj(
+    sev_info = SEVInfo.model_validate(
         {
             "enabled": True,
             "api_major": 1,
diff --git a/tests/unit/test_vm_client.py b/tests/unit/test_vm_client.py
index 7cc9a2c3..d9a9a36b 100644
--- a/tests/unit/test_vm_client.py
+++ b/tests/unit/test_vm_client.py
@@ -290,8 +290,8 @@ async def test_vm_client_generate_correct_authentication_headers():
     )
 
     path, headers = await vm_client._generate_header(vm_id, "reboot", method="post")
-    signed_pubkey = SignedPubKeyHeader.parse_raw(headers["X-SignedPubKey"])
-    signed_operation = SignedOperation.parse_raw(headers["X-SignedOperation"])
+    signed_pubkey = SignedPubKeyHeader.model_validate_json(headers["X-SignedPubKey"])
+    signed_operation = SignedOperation.model_validate_json(headers["X-SignedOperation"])
     address = verify_signed_operation(signed_operation, signed_pubkey)
 
     assert vm_client.account.get_address() == address

From 660762f1ac72c7386310aae339b5a207c7e5e56a Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 4 Oct 2024 11:46:22 +0900
Subject: [PATCH 02/43] fix: lint tests were failing

- Updated all instances of **extra_fields to ensure proper handling of
Optional dictionaries using `(extra_fields or {})` pattern.
- Added proper return statements in `AlephHttpClient.get_message_status`
to return parsed JSON data as a `MessageStatus` object.
- Updated `Settings` class in `conf.py` to correct DNS resolvers type
and simplify the `model_config` definition.
- Refactored `parse_volume` to ensure correct handling of Mapping types
and MachineVolume types, avoiding TypeErrors.
- Improved field validation and model validation in `SignedPubKeyHeader` by
using correct Pydantic v2 validation decorators and ensuring compatibility with the new model behavior.
- Applied formatting and consistency fixes for `model_dump` usage and indentation improvements in test files.
---
 src/aleph/sdk/client/authenticated_http.py |  2 +-
 src/aleph/sdk/client/http.py               |  3 +++
 src/aleph/sdk/conf.py                      | 11 ++++------
 src/aleph/sdk/utils.py                     | 25 +++++++++++-----------
 tests/unit/aleph_vm_authentication.py      |  8 +++----
 tests/unit/conftest.py                     |  4 +++-
 tests/unit/test_utils.py                   | 19 ++++++++--------
 7 files changed, 35 insertions(+), 37 deletions(-)

diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py
index 2f30e534..ad15ca09 100644
--- a/src/aleph/sdk/client/authenticated_http.py
+++ b/src/aleph/sdk/client/authenticated_http.py
@@ -710,7 +710,7 @@ async def _upload_file_native(
             item_hash=file_hash,
             mime_type=mime_type,
             time=time.time(),
-            **extra_fields,
+            **(extra_fields or {}),
         )
         message, _ = await self._storage_push_file_with_message(
             file_content=file_content,
diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py
index 60e65bb2..a59531c6 100644
--- a/src/aleph/sdk/client/http.py
+++ b/src/aleph/sdk/client/http.py
@@ -467,3 +467,6 @@ async def get_message_status(self, item_hash: str) -> MessageStatus:
             if resp.status == HTTPNotFound.status_code:
                 raise MessageNotFoundError(f"No such hash {item_hash}")
             resp.raise_for_status()
+
+        data = await resp.json()
+        return MessageStatus(**data)
diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index e9a46293..fbf77e53 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,11 +5,10 @@
 from shutil import which
 from typing import ClassVar, Dict, Optional, Union
 
-from pydantic_settings import BaseSettings
-
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
 from pydantic import BaseModel, ConfigDict, Field
+from pydantic_settings import BaseSettings
 
 from aleph.sdk.types import ChainInfo
 
@@ -142,12 +141,10 @@ class Settings(BaseSettings):
     DNS_PROGRAM_DOMAIN: ClassVar[str] = "program.public.aleph.sh"
     DNS_INSTANCE_DOMAIN: ClassVar[str] = "instance.public.aleph.sh"
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
-    DNS_RESOLVERS: ClassVar[str] = ["9.9.9.9", "1.1.1.1"]
+    DNS_RESOLVERS: ClassVar[list[str]] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
-       env_prefix="ALEPH_",
-       case_sensitive=False,
-       env_file=".env"
+        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
     )
 
 
@@ -159,7 +156,7 @@ class MainConfiguration(BaseModel):
     path: Path
     chain: Chain
 
-    model_config = ConfigDict(use_enum_values = True)
+    model_config = ConfigDict(use_enum_values=True)
 
 
 # Settings singleton
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index 1cdb6608..9f69d2d7 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -28,15 +28,13 @@
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
 
-from pydantic import BaseModel
-
+import pydantic_core
 from aleph_message.models import ItemHash, MessageType
 from aleph_message.models.execution.program import Encoding
 from aleph_message.models.execution.volume import MachineVolume
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
-import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
@@ -181,17 +179,18 @@ def extended_json_encoder(obj: Any) -> Any:
 def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
     # Python 3.9 does not support `isinstance(volume_dict, MachineVolume)`,
     # so we need to iterate over all types.
-    if any(
-        isinstance(volume_dict, volume_type) for volume_type in get_args(MachineVolume)
-    ):
-        return volume_dict
     for volume_type in get_args(MachineVolume):
-        try:
-            return volume_type.model_validate(volume_dict)
-        except ValueError:
-            continue
-    else:
-        raise ValueError(f"Could not parse volume: {volume_dict}")
+        if isinstance(volume_dict, volume_type):
+            return volume_dict
+
+    if isinstance(volume_dict, Mapping):
+        for volume_type in get_args(MachineVolume):
+            try:
+                return volume_type(**volume_dict)
+            except (TypeError, ValueError):
+                continue
+
+    raise ValueError("Invalid volume data, could not be parsed into a MachineVolume")
 
 
 def compute_sha256(s: str) -> str:
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index 86a4cd20..4a048b50 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -13,7 +13,7 @@
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 from jwcrypto.jwa import JWA
-from pydantic import BaseModel, ValidationError, model_validator, field_validator
+from pydantic import BaseModel, ValidationError, field_validator, model_validator
 
 from aleph.sdk.utils import bytes_from_hex
 
@@ -66,17 +66,15 @@ class SignedPubKeyHeader(BaseModel):
     @field_validator("signature")
     def signature_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the signature from hexadecimal to bytes"""
-
         return bytes_from_hex(value.decode())
 
     @field_validator("payload")
     def payload_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the payload from hexadecimal to bytes"""
-
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values) -> Dict[str, bytes]:
+    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -88,7 +86,7 @@ def check_expiry(cls, values) -> Dict[str, bytes]:
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
+    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         """Check that the signature is valid"""
         signature: bytes = values.signature
         payload: bytes = values.payload
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index bea0d886..385d2836 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -135,7 +135,9 @@ def json_post() -> dict:
 def raw_messages_response(aleph_messages) -> Callable[[int], Dict[str, Any]]:
     return lambda page: {
         "messages": (
-            [message.model_dump() for message in aleph_messages] if int(page) == 1 else []
+            [message.model_dump() for message in aleph_messages]
+            if int(page) == 1
+            else []
         ),
         "pagination_item": "messages",
         "pagination_page": int(page),
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index df27ee2f..8e4083c0 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -12,7 +12,6 @@
     ProgramMessage,
     StoreMessage,
 )
-from aleph_message.models.execution.environment import MachineResources
 from aleph_message.models.execution.volume import (
     EphemeralVolume,
     ImmutableVolume,
@@ -115,17 +114,17 @@ def test_enum_as_str():
         (
             MessageType.aggregate,
             {
-                'address': '0x1',
-                'content': {
-                    'Hello': {
-                        'vcpus': 1,
-                        'memory': 1024,
-                        'seconds': 1,
-                        'published_ports': None,
+                "address": "0x1",
+                "content": {
+                    "Hello": {
+                        "vcpus": 1,
+                        "memory": 1024,
+                        "seconds": 1,
+                        "published_ports": None,
                     },
                 },
-                'key': 'test',
-                'time': 1.0,
+                "key": "test",
+                "time": 1.0,
             },
         ),
     ],

From 4908fbedc5af2640ccc869a27445145fd1797b1d Mon Sep 17 00:00:00 2001
From: Laurent Peuch <cortex@worlddomination.be>
Date: Wed, 25 Sep 2024 23:18:08 +0200
Subject: [PATCH 03/43] feat: add pyproject-fmt

---
 pyproject.toml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pyproject.toml b/pyproject.toml
index 8a7bbc09..1c7ae2da 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -154,6 +154,7 @@ dependencies = [
     "mypy-extensions==1.0.0",
     "ruff==0.4.8",
     "isort==5.13.2",
+    "pyproject-fmt==2.2.1",
 ]
 [tool.hatch.envs.linting.scripts]
 typing = "mypy --config-file=pyproject.toml {args:} ./src/ ./tests/ ./examples/"
@@ -161,11 +162,13 @@ style = [
     "ruff check {args:.} ./src/ ./tests/ ./examples/",
     "black --check --diff {args:} ./src/ ./tests/ ./examples/",
     "isort --check-only --profile black {args:} ./src/ ./tests/ ./examples/",
+    "pyproject-fmt --check pyproject.toml",
 ]
 fmt = [
     "black {args:} ./src/ ./tests/ ./examples/",
     "ruff check --fix {args:.} ./src/ ./tests/ ./examples/",
     "isort --profile black {args:} ./src/ ./tests/ ./examples/",
+    "pyproject-fmt pyproject.toml",
     "style",
 ]
 all = [

From e50fabbec67e37e7cdb65b69ab5adf963d9edb63 Mon Sep 17 00:00:00 2001
From: Laurent Peuch <cortex@worlddomination.be>
Date: Wed, 25 Sep 2024 23:19:03 +0200
Subject: [PATCH 04/43] fix: run pyproject-fmt

---
 pyproject.toml | 321 +++++++++++++++++++++++++------------------------
 1 file changed, 161 insertions(+), 160 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 1c7ae2da..409694cf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,138 +1,137 @@
 [build-system]
-requires = ["hatchling", "hatch-vcs"]
 build-backend = "hatchling.build"
 
+requires = [ "hatch-vcs", "hatchling" ]
+
 [project]
 name = "aleph-sdk-python"
-dynamic = ["version"]
 description = "Lightweight Python Client library for the Aleph.im network"
 readme = "README.md"
 license = { file = "LICENSE.txt" }
 authors = [
-    { name = "Aleph.im Team", email = "hello@aleph.im" },
+  { name = "Aleph.im Team", email = "hello@aleph.im" },
 ]
 classifiers = [
-    "Programming Language :: Python :: 3",
-    "Development Status :: 4 - Beta",
-    "Framework :: aiohttp",
-    "Intended Audience :: Developers",
-    "License :: OSI Approved :: MIT License",
-    "Operating System :: POSIX :: Linux",
-    "Operating System :: MacOS :: MacOS X",
-    "Topic :: Software Development :: Libraries",
-]
+  "Development Status :: 4 - Beta",
+  "Framework :: aiohttp",
+  "Intended Audience :: Developers",
+  "License :: OSI Approved :: MIT License",
+  "Operating System :: MacOS :: MacOS X",
+  "Operating System :: POSIX :: Linux",
+  "Programming Language :: Python :: 3 :: Only",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
+  "Topic :: Software Development :: Libraries",
+]
+dynamic = [ "version" ]
 dependencies = [
-    "aiohttp>=3.8.3",
-    "aleph-message>=0.4.9",
-    "coincurve; python_version<\"3.11\"",
-    "coincurve>=19.0.0; python_version>=\"3.11\"",
-    "eth_abi>=4.0.0; python_version>=\"3.11\"",
-    "jwcrypto==1.5.6",
-    "python-magic",
-    "pydantic-settings",
-    "typing_extensions",
-    "aioresponses>=0.7.6",
-    "aleph-superfluid>=0.2.1",
-    "eth_typing==4.3.1",
-    "web3==6.3.0",
-    "base58==2.1.1", # Needed now as default with _load_account changement
-    "pynacl==1.5.0" # Needed now as default with _load_account changement
-]
-
-[project.optional-dependencies]
-cosmos = [
-    "cosmospy",
-]
-dns = [
-    "aiodns",
-]
-docs = [
-    "sphinxcontrib-plantuml",
-]
-ledger = [
-    "ledgereth==0.9.1",
-]
-mqtt = [
-    "aiomqtt<=0.1.3",
-    "certifi",
-    "Click",
-]
-nuls2 = [
-    "aleph-nuls2",
-]
-substrate = [
-    "py-sr25519-bindings",
-    "substrate-interface",
-]
-solana = [
-    "base58",
-    "pynacl",
-]
-tezos = [
-    "aleph-pytezos==0.1.1",
-    "pynacl",
-]
-encryption = [
-    "eciespy; python_version<\"3.11\"",
-    "eciespy>=0.3.13; python_version>=\"3.11\"",
+  "aiohttp>=3.8.3",
+  "aioresponses>=0.7.6",
+  "aleph-message>=0.4.9",
+  "aleph-superfluid>=0.2.1",
+  "base58==2.1.1",                         # Needed now as default with _load_account changement
+  "coincurve; python_version<'3.11'",
+  "coincurve>=19; python_version>='3.11'",
+  "eth-abi>=4; python_version>='3.11'",
+  "eth-typing==4.3.1",
+  "jwcrypto==1.5.6",
+  "pynacl==1.5",                           # Needed now as default with _load_account changement
+  "python-magic",
+  "typing-extensions",
+  "web3==6.3",
 ]
-all = [
-    "aleph-sdk-python[cosmos,dns,docs,ledger,mqtt,nuls2,substrate,solana,tezos,encryption]",
+
+optional-dependencies.all = [
+  "aleph-sdk-python[cosmos,dns,docs,ledger,mqtt,nuls2,substrate,solana,tezos,encryption]",
+]
+optional-dependencies.cosmos = [
+  "cosmospy",
+]
+optional-dependencies.dns = [
+  "aiodns",
+]
+optional-dependencies.docs = [
+  "sphinxcontrib-plantuml",
+]
+optional-dependencies.encryption = [
+  "eciespy; python_version<'3.11'",
+  "eciespy>=0.3.13; python_version>='3.11'",
 ]
+optional-dependencies.ledger = [
+  "ledgereth==0.9.1",
+]
+optional-dependencies.mqtt = [
+  "aiomqtt<=0.1.3",
+  "certifi",
+  "click",
+]
+optional-dependencies.nuls2 = [
+  "aleph-nuls2",
+]
+optional-dependencies.solana = [
+  "base58",
+  "pynacl",
+]
+optional-dependencies.substrate = [
+  "py-sr25519-bindings",
+  "substrate-interface",
+]
+optional-dependencies.tezos = [
+  "aleph-pytezos==0.1.1",
+  "pynacl",
+]
+urls.Documentation = "https://aleph.im/"
+urls.Homepage = "https://github.com/aleph-im/aleph-sdk-python"
 
 [tool.hatch.metadata]
 allow-direct-references = true
 
-[project.urls]
-Documentation = "https://aleph.im/"
-Homepage = "https://github.com/aleph-im/aleph-sdk-python"
-
 [tool.hatch.version]
 source = "vcs"
 
 [tool.hatch.build.targets.wheel]
 packages = [
-    "src/aleph",
-    "pyproject.toml",
-    "README.md",
-    "LICENSE.txt",
+  "src/aleph",
+  "pyproject.toml",
+  "README.md",
+  "LICENSE.txt",
 ]
 
 [tool.hatch.build.targets.sdist]
 include = [
-    "src/aleph",
-    "pyproject.toml",
-    "README.md",
-    "LICENSE.txt",
+  "src/aleph",
+  "pyproject.toml",
+  "README.md",
+  "LICENSE.txt",
 ]
 
-[tool.isort]
-profile = "black"
-
 [[tool.hatch.envs.all.matrix]]
-python = ["3.9", "3.10", "3.11"]
+python = [ "3.9", "3.10", "3.11" ]
 
 [tool.hatch.envs.testing]
 features = [
-    "cosmos",
-    "dns",
-    "ledger",
-    "nuls2",
-    "substrate",
-    "solana",
-    "tezos",
-    "encryption",
+  "cosmos",
+  "dns",
+  "ledger",
+  "nuls2",
+  "substrate",
+  "solana",
+  "tezos",
+  "encryption",
 ]
 dependencies = [
-    "pytest==8.0.1",
-    "pytest-cov==4.1.0",
-    "pytest-mock==3.12.0",
-    "pytest-asyncio==0.23.5",
-    "pytest-aiohttp==1.0.5",
-    "aioresponses==0.7.6",
-    "fastapi",
-    "httpx",
-    "secp256k1",
+  "pytest==8.0.1",
+  "pytest-cov==4.1.0",
+  "pytest-mock==3.12.0",
+  "pytest-asyncio==0.23.5",
+  "pytest-aiohttp==1.0.5",
+  "aioresponses==0.7.6",
+  "fastapi",
+  "httpx",
+  "secp256k1",
 ]
 [tool.hatch.envs.testing.scripts]
 test = "pytest {args:} ./src/ ./tests/ ./examples/"
@@ -149,101 +148,103 @@ cov = [
 [tool.hatch.envs.linting]
 detached = true
 dependencies = [
-    "black==24.2.0",
-    "mypy==1.9.0",
-    "mypy-extensions==1.0.0",
-    "ruff==0.4.8",
-    "isort==5.13.2",
-    "pyproject-fmt==2.2.1",
+  "black==24.2.0",
+  "mypy==1.9.0",
+  "mypy-extensions==1.0.0",
+  "ruff==0.4.8",
+  "isort==5.13.2",
+  "pyproject-fmt==2.2.1",
 ]
 [tool.hatch.envs.linting.scripts]
 typing = "mypy --config-file=pyproject.toml {args:} ./src/ ./tests/ ./examples/"
 style = [
-    "ruff check {args:.} ./src/ ./tests/ ./examples/",
-    "black --check --diff {args:} ./src/ ./tests/ ./examples/",
-    "isort --check-only --profile black {args:} ./src/ ./tests/ ./examples/",
-    "pyproject-fmt --check pyproject.toml",
+  "ruff check {args:.} ./src/ ./tests/ ./examples/",
+  "black --check --diff {args:} ./src/ ./tests/ ./examples/",
+  "isort --check-only --profile black {args:} ./src/ ./tests/ ./examples/",
+  "pyproject-fmt --check pyproject.toml",
 ]
 fmt = [
-    "black {args:} ./src/ ./tests/ ./examples/",
-    "ruff check --fix {args:.} ./src/ ./tests/ ./examples/",
-    "isort --profile black {args:} ./src/ ./tests/ ./examples/",
-    "pyproject-fmt pyproject.toml",
-    "style",
+  "black {args:} ./src/ ./tests/ ./examples/",
+  "ruff check --fix {args:.} ./src/ ./tests/ ./examples/",
+  "isort --profile black {args:} ./src/ ./tests/ ./examples/",
+  "pyproject-fmt pyproject.toml",
+  "style",
 ]
 all = [
-    "style",
-    "typing",
-]
-
-[tool.mypy]
-python_version = 3.9
-mypy_path = "src"
-exclude = [
-    "conftest.py"
+  "style",
+  "typing",
 ]
-show_column_numbers = true
-check_untyped_defs = true
-
-# Import discovery
-# Install types for third-party library stubs (e.g. from typeshed repository)
-install_types = true
-non_interactive = true
-# Suppresses error messages about imports that cannot be resolved (no py.typed file, no stub file, etc).
-ignore_missing_imports = true
-# Don't follow imports
-follow_imports = "silent"
 
-
-# Miscellaneous strictness flags
-# Allows variables to be redefined with an arbitrary type, as long as the redefinition is in the same block and nesting level as the original definition.
-allow_redefinition = true
+[tool.isort]
+profile = "black"
 
 [tool.pytest.ini_options]
 minversion = "6.0"
-pythonpath = ["src"]
+pythonpath = [ "src" ]
 addopts = "-vv -m \"not ledger_hardware\""
-norecursedirs = ["*.egg", "dist", "build", ".tox", ".venv", "*/site-packages/*"]
-testpaths = ["tests/unit"]
-markers = {ledger_hardware = "marks tests as requiring ledger hardware"}
+norecursedirs = [ "*.egg", "dist", "build", ".tox", ".venv", "*/site-packages/*" ]
+testpaths = [ "tests/unit" ]
+markers = { ledger_hardware = "marks tests as requiring ledger hardware" }
 
 [tool.coverage.run]
 branch = true
 parallel = true
 source = [
-    "src/",
+  "src/",
 ]
 omit = [
-    "*/site-packages/*",
+  "*/site-packages/*",
 ]
 
 [tool.coverage.paths]
 source = [
-    "src/",
+  "src/",
 ]
 omit = [
-    "*/site-packages/*",
+  "*/site-packages/*",
 ]
 
 [tool.coverage.report]
 show_missing = true
 skip_empty = true
 exclude_lines = [
-    # Have to re-enable the standard pragma
-    "pragma: no cover",
+  # Have to re-enable the standard pragma
+  "pragma: no cover",
 
-    # Don't complain about missing debug-only code:
-    "def __repr__",
-    "if self\\.debug",
+  # Don't complain about missing debug-only code:
+  "def __repr__",
+  "if self\\.debug",
 
-    # Don't complain if tests don't hit defensive assertion code:
-    "raise AssertionError",
-    "raise NotImplementedError",
+  # Don't complain if tests don't hit defensive assertion code:
+  "raise AssertionError",
+  "raise NotImplementedError",
 
-    # Don't complain if non-runnable code isn't run:
-    "if 0:",
-    "if __name__ == .__main__.:",
+  # Don't complain if non-runnable code isn't run:
+  "if 0:",
+  "if __name__ == .__main__.:",
+
+  # Don't complain about ineffective code:
+  "pass",
+]
 
-    # Don't complain about ineffective code:
-    "pass",
+[tool.mypy]
+python_version = 3.9
+mypy_path = "src"
+exclude = [
+  "conftest.py",
 ]
+show_column_numbers = true
+check_untyped_defs = true
+
+# Import discovery
+# Install types for third-party library stubs (e.g. from typeshed repository)
+install_types = true
+non_interactive = true
+# Suppresses error messages about imports that cannot be resolved (no py.typed file, no stub file, etc).
+ignore_missing_imports = true
+# Don't follow imports
+follow_imports = "silent"
+
+# Miscellaneous strictness flags
+# Allows variables to be redefined with an arbitrary type, as long as the redefinition is in the same block and nesting level as the original definition.
+allow_redefinition = true

From 29b3a1bb7cbe0f38d07e88ba9780a4009a835eb7 Mon Sep 17 00:00:00 2001
From: philogicae <philogicae+github@gmail.com>
Date: Fri, 11 Oct 2024 20:01:12 +0300
Subject: [PATCH 05/43] Post-SOL fixes (#178)

* Missing chain field on auth

* Fix Signature of Solana operation for CRN

* Add export_private_key func for accounts

* Improve _load_account

* Add chain arg to _load_account

* Increase default HTTP_REQUEST_TIMEOUT

* Typing

---------

Co-authored-by: Olivier Le Thanh Duong <olivier@lethanh.be>
---
 src/aleph/sdk/account.py          | 108 +++++++++++++++++++-----------
 src/aleph/sdk/chains/ethereum.py  |   5 ++
 src/aleph/sdk/chains/solana.py    |   6 ++
 src/aleph/sdk/client/vm_client.py |  17 +++--
 src/aleph/sdk/conf.py             |   2 +-
 src/aleph/sdk/types.py            |   4 ++
 6 files changed, 97 insertions(+), 45 deletions(-)

diff --git a/src/aleph/sdk/account.py b/src/aleph/sdk/account.py
index 8c067283..9bfafcd3 100644
--- a/src/aleph/sdk/account.py
+++ b/src/aleph/sdk/account.py
@@ -10,67 +10,95 @@
 from aleph.sdk.chains.remote import RemoteAccount
 from aleph.sdk.chains.solana import SOLAccount
 from aleph.sdk.conf import load_main_configuration, settings
+from aleph.sdk.evm_utils import get_chains_with_super_token
 from aleph.sdk.types import AccountFromPrivateKey
 
 logger = logging.getLogger(__name__)
 
 T = TypeVar("T", bound=AccountFromPrivateKey)
 
+chain_account_map: Dict[Chain, Type[T]] = {  # type: ignore
+    Chain.ETH: ETHAccount,
+    Chain.AVAX: ETHAccount,
+    Chain.BASE: ETHAccount,
+    Chain.SOL: SOLAccount,
+}
+
 
 def load_chain_account_type(chain: Chain) -> Type[AccountFromPrivateKey]:
-    chain_account_map: Dict[Chain, Type[AccountFromPrivateKey]] = {
-        Chain.ETH: ETHAccount,
-        Chain.AVAX: ETHAccount,
-        Chain.SOL: SOLAccount,
-        Chain.BASE: ETHAccount,
-    }
-    return chain_account_map.get(chain) or ETHAccount
+    return chain_account_map.get(chain) or ETHAccount  # type: ignore
 
 
-def account_from_hex_string(private_key_str: str, account_type: Type[T]) -> T:
+def account_from_hex_string(
+    private_key_str: str,
+    account_type: Optional[Type[T]],
+    chain: Optional[Chain] = None,
+) -> AccountFromPrivateKey:
     if private_key_str.startswith("0x"):
         private_key_str = private_key_str[2:]
-    return account_type(bytes.fromhex(private_key_str))
 
+    if not chain:
+        if not account_type:
+            account_type = load_chain_account_type(Chain.ETH)  # type: ignore
+        return account_type(bytes.fromhex(private_key_str))  # type: ignore
+
+    account_type = load_chain_account_type(chain)
+    account = account_type(bytes.fromhex(private_key_str))
+    if chain in get_chains_with_super_token():
+        account.switch_chain(chain)
+    return account  # type: ignore
 
-def account_from_file(private_key_path: Path, account_type: Type[T]) -> T:
+
+def account_from_file(
+    private_key_path: Path,
+    account_type: Optional[Type[T]],
+    chain: Optional[Chain] = None,
+) -> AccountFromPrivateKey:
     private_key = private_key_path.read_bytes()
-    return account_type(private_key)
+
+    if not chain:
+        if not account_type:
+            account_type = load_chain_account_type(Chain.ETH)  # type: ignore
+        return account_type(private_key)  # type: ignore
+
+    account_type = load_chain_account_type(chain)
+    account = account_type(private_key)
+    if chain in get_chains_with_super_token():
+        account.switch_chain(chain)
+    return account
 
 
 def _load_account(
     private_key_str: Optional[str] = None,
     private_key_path: Optional[Path] = None,
     account_type: Optional[Type[AccountFromPrivateKey]] = None,
+    chain: Optional[Chain] = None,
 ) -> AccountFromPrivateKey:
-    """Load private key from a string or a file. takes the string argument in priority"""
-    if private_key_str or (private_key_path and private_key_path.is_file()):
-        if account_type:
-            if private_key_path and private_key_path.is_file():
-                return account_from_file(private_key_path, account_type)
-            elif private_key_str:
-                return account_from_hex_string(private_key_str, account_type)
-            else:
-                raise ValueError("Any private key specified")
+    """Load an account from a private key string or file, or from the configuration file."""
+
+    # Loads configuration if no account_type is specified
+    if not account_type:
+        config = load_main_configuration(settings.CONFIG_FILE)
+        if config and hasattr(config, "chain"):
+            account_type = load_chain_account_type(config.chain)
+            logger.debug(
+                f"Detected {config.chain} account for path {settings.CONFIG_FILE}"
+            )
         else:
-            main_configuration = load_main_configuration(settings.CONFIG_FILE)
-            if main_configuration:
-                account_type = load_chain_account_type(main_configuration.chain)
-                logger.debug(
-                    f"Detected {main_configuration.chain} account for path {settings.CONFIG_FILE}"
-                )
-            else:
-                account_type = ETHAccount  # Defaults to ETHAccount
-                logger.warning(
-                    f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type.__name__}"
-                )
-            if private_key_path and private_key_path.is_file():
-                return account_from_file(private_key_path, account_type)
-            elif private_key_str:
-                return account_from_hex_string(private_key_str, account_type)
-            else:
-                raise ValueError("Any private key specified")
+            account_type = account_type = load_chain_account_type(
+                Chain.ETH
+            )  # Defaults to ETHAccount
+            logger.warning(
+                f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type and account_type.__name__}"
+            )
 
+    # Loads private key from a string
+    if private_key_str:
+        return account_from_hex_string(private_key_str, account_type, chain)
+    # Loads private key from a file
+    elif private_key_path and private_key_path.is_file():
+        return account_from_file(private_key_path, account_type, chain)
+    # For ledger keys
     elif settings.REMOTE_CRYPTO_HOST:
         logger.debug("Using remote account")
         loop = asyncio.get_event_loop()
@@ -80,10 +108,12 @@ def _load_account(
                 unix_socket=settings.REMOTE_CRYPTO_UNIX_SOCKET,
             )
         )
+    # Fallback: config.path if set, else generate a new private key
     else:
-        account_type = ETHAccount  # Defaults to ETHAccount
         new_private_key = get_fallback_private_key()
-        account = account_type(private_key=new_private_key)
+        account = account_from_hex_string(
+            bytes.hex(new_private_key), account_type, chain
+        )
         logger.info(
             f"Generated fallback private key with address {account.get_address()}"
         )
diff --git a/src/aleph/sdk/chains/ethereum.py b/src/aleph/sdk/chains/ethereum.py
index 32f459b7..ab93df56 100644
--- a/src/aleph/sdk/chains/ethereum.py
+++ b/src/aleph/sdk/chains/ethereum.py
@@ -1,4 +1,5 @@
 import asyncio
+import base64
 from decimal import Decimal
 from pathlib import Path
 from typing import Awaitable, Optional, Union
@@ -61,6 +62,10 @@ def from_mnemonic(mnemonic: str, chain: Optional[Chain] = None) -> "ETHAccount":
             private_key=Account.from_mnemonic(mnemonic=mnemonic).key, chain=chain
         )
 
+    def export_private_key(self) -> str:
+        """Export the private key using standard format."""
+        return f"0x{base64.b16encode(self.private_key).decode().lower()}"
+
     def get_address(self) -> str:
         return self._account.address
 
diff --git a/src/aleph/sdk/chains/solana.py b/src/aleph/sdk/chains/solana.py
index a9352489..920ca8a0 100644
--- a/src/aleph/sdk/chains/solana.py
+++ b/src/aleph/sdk/chains/solana.py
@@ -43,6 +43,12 @@ async def sign_raw(self, buffer: bytes) -> bytes:
         sig = self._signing_key.sign(buffer)
         return sig.signature
 
+    def export_private_key(self) -> str:
+        """Export the private key using Phantom format."""
+        return base58.b58encode(
+            self.private_key + self._signing_key.verify_key.encode()
+        ).decode()
+
     def get_address(self) -> str:
         return encode(self._signing_key.verify_key)
 
diff --git a/src/aleph/sdk/client/vm_client.py b/src/aleph/sdk/client/vm_client.py
index 18d280cc..83b00dc9 100644
--- a/src/aleph/sdk/client/vm_client.py
+++ b/src/aleph/sdk/client/vm_client.py
@@ -5,10 +5,11 @@
 from urllib.parse import urlparse
 
 import aiohttp
-from aleph_message.models import ItemHash
+from aleph_message.models import Chain, ItemHash
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 
+from aleph.sdk.chains.solana import SOLAccount
 from aleph.sdk.types import Account
 from aleph.sdk.utils import (
     create_vm_control_payload,
@@ -36,11 +37,13 @@ def __init__(
         self.account = account
         self.ephemeral_key = jwk.JWK.generate(kty="EC", crv="P-256")
         self.node_url = node_url.rstrip("/")
-        self.pubkey_payload = self._generate_pubkey_payload()
+        self.pubkey_payload = self._generate_pubkey_payload(
+            Chain.SOL if isinstance(account, SOLAccount) else Chain.ETH
+        )
         self.pubkey_signature_header = ""
         self.session = session or aiohttp.ClientSession()
 
-    def _generate_pubkey_payload(self) -> Dict[str, Any]:
+    def _generate_pubkey_payload(self, chain: Chain = Chain.ETH) -> Dict[str, Any]:
         return {
             "pubkey": json.loads(self.ephemeral_key.export_public()),
             "alg": "ECDSA",
@@ -50,12 +53,16 @@ def _generate_pubkey_payload(self) -> Dict[str, Any]:
                 datetime.datetime.utcnow() + datetime.timedelta(days=1)
             ).isoformat()
             + "Z",
+            "chain": chain.value,
         }
 
     async def _generate_pubkey_signature_header(self) -> str:
         pubkey_payload = json.dumps(self.pubkey_payload).encode("utf-8").hex()
-        signable_message = encode_defunct(hexstr=pubkey_payload)
-        buffer_to_sign = signable_message.body
+        if isinstance(self.account, SOLAccount):
+            buffer_to_sign = bytes(pubkey_payload, encoding="utf-8")
+        else:
+            signable_message = encode_defunct(hexstr=pubkey_payload)
+            buffer_to_sign = signable_message.body
 
         signed_message = await self.account.sign_raw(buffer_to_sign)
         pubkey_signature = to_0x_hex(signed_message)
diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index fbf77e53..090c290b 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -42,7 +42,7 @@ class Settings(BaseSettings):
     REMOTE_CRYPTO_HOST: Optional[str] = None
     REMOTE_CRYPTO_UNIX_SOCKET: Optional[str] = None
     ADDRESS_TO_USE: Optional[str] = None
-    HTTP_REQUEST_TIMEOUT: ClassVar[float] = 10.0
+    HTTP_REQUEST_TIMEOUT = 15.0
 
     DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS"
     DEFAULT_RUNTIME_ID: str = (
diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py
index 081a3465..dab90379 100644
--- a/src/aleph/sdk/types.py
+++ b/src/aleph/sdk/types.py
@@ -39,6 +39,10 @@ def __init__(self, private_key: bytes): ...
 
     async def sign_raw(self, buffer: bytes) -> bytes: ...
 
+    def export_private_key(self) -> str: ...
+
+    def switch_chain(self, chain: Optional[str] = None) -> None: ...
+
 
 GenericMessage = TypeVar("GenericMessage", bound=AlephMessage)
 

From cd2f8e465dcdd6f57fdbbfa06411b2a025f64908 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 4 Oct 2024 10:59:02 +0900
Subject: [PATCH 06/43] Migrate to Pydantic v2, update model validation and fix
 async issues

- Migrated to Pydantic v2:
  - Replaced deprecated `parse_obj()` and `parse_raw()` with
`model_validate()` and `model_validate_json()`.
  - Replaced `.dict()` with `.model_dump()` for serializing models to dictionaries.
  - Updated `validator` to `field_validator` and `root_validator` to
`model_validator` to comply with Pydantic v2 syntax changes.

- Fixed asyncio issues:
  - Added `await` for asynchronous methods like `raise_for_status()`
in `RemoteAccount` and other HTTP operations to avoid `RuntimeWarning`.

- Updated config handling:
  - Used `ClassVar` for constants in `Settings` and other configuration classes.
  - Replaced `Config` with `ConfigDict` in Pydantic models
to follow v2 conventions.
  - Added default values for missing fields in chain configurations
(`CHAINS_SEPOLIA_ACTIVE`, etc.).

- Adjusted signature handling:
  - Updated the signing logic to prepend `0x` in the `BaseAccount` signature
generation to ensure correct Ethereum address formatting.

- Minor fixes:
  - Resolved issue with extra fields not being allowed by default
by specifying `extra="allow"` or `extra="forbid"` where necessary.
  - Fixed tests to account for changes in model validation and
serialization behavior.
  - Added `pydantic-settings` as a new dependency for configuration management.
---
 src/aleph/sdk/conf.py                 |  9 ++++++---
 src/aleph/sdk/utils.py                | 21 +++++++++------------
 tests/unit/aleph_vm_authentication.py |  6 +++---
 3 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 090c290b..8f158aef 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,10 +5,11 @@
 from shutil import which
 from typing import ClassVar, Dict, Optional, Union
 
+from pydantic_settings import BaseSettings
+
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
 from pydantic import BaseModel, ConfigDict, Field
-from pydantic_settings import BaseSettings
 
 from aleph.sdk.types import ChainInfo
 
@@ -141,10 +142,12 @@ class Settings(BaseSettings):
     DNS_PROGRAM_DOMAIN: ClassVar[str] = "program.public.aleph.sh"
     DNS_INSTANCE_DOMAIN: ClassVar[str] = "instance.public.aleph.sh"
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
-    DNS_RESOLVERS: ClassVar[list[str]] = ["9.9.9.9", "1.1.1.1"]
+    DNS_RESOLVERS: ClassVar[str] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
-        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
+       env_prefix="ALEPH_",
+       case_sensitive=False,
+       env_file=".env"
     )
 
 
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index 9f69d2d7..f9380c1a 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -28,13 +28,15 @@
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
 
-import pydantic_core
+from pydantic import BaseModel
+
 from aleph_message.models import ItemHash, MessageType
 from aleph_message.models.execution.program import Encoding
 from aleph_message.models.execution.volume import MachineVolume
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
+import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
@@ -180,17 +182,12 @@ def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
     # Python 3.9 does not support `isinstance(volume_dict, MachineVolume)`,
     # so we need to iterate over all types.
     for volume_type in get_args(MachineVolume):
-        if isinstance(volume_dict, volume_type):
-            return volume_dict
-
-    if isinstance(volume_dict, Mapping):
-        for volume_type in get_args(MachineVolume):
-            try:
-                return volume_type(**volume_dict)
-            except (TypeError, ValueError):
-                continue
-
-    raise ValueError("Invalid volume data, could not be parsed into a MachineVolume")
+        try:
+            return volume_type.model_validate(volume_dict)
+        except ValueError:
+            continue
+    else:
+        raise ValueError(f"Could not parse volume: {volume_dict}")
 
 
 def compute_sha256(s: str) -> str:
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index 4a048b50..4c435482 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -13,7 +13,7 @@
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 from jwcrypto.jwa import JWA
-from pydantic import BaseModel, ValidationError, field_validator, model_validator
+from pydantic import BaseModel, ValidationError, model_validator, field_validator
 
 from aleph.sdk.utils import bytes_from_hex
 
@@ -74,7 +74,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_expiry(cls, values) -> Dict[str, bytes]:
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -86,7 +86,7 @@ def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
         """Check that the signature is valid"""
         signature: bytes = values.signature
         payload: bytes = values.payload

From a86da5fcf38638682063d661406cba2d476ee40a Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 15 Oct 2024 10:14:50 +0900
Subject: [PATCH 07/43] fix: add explicit float type for HTTP_REQUEST_TIMEOUT
 to comply with Pydantic v2 requirements

Pydantic v2 requires explicit type annotations for fields, so added `float`
to ensure proper validation of HTTP_REQUEST_TIMEOUT.
---
 src/aleph/sdk/conf.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 8f158aef..56686c42 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -43,7 +43,7 @@ class Settings(BaseSettings):
     REMOTE_CRYPTO_HOST: Optional[str] = None
     REMOTE_CRYPTO_UNIX_SOCKET: Optional[str] = None
     ADDRESS_TO_USE: Optional[str] = None
-    HTTP_REQUEST_TIMEOUT = 15.0
+    HTTP_REQUEST_TIMEOUT: float = 15.0
 
     DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS"
     DEFAULT_RUNTIME_ID: str = (

From fa8e7c84f85dc1ab2c40b4849669f57cfad3b198 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 15 Oct 2024 12:02:00 +0900
Subject: [PATCH 08/43] Fix: Linting tests did not pass:

---
 src/aleph/sdk/conf.py                 | 9 +++------
 src/aleph/sdk/utils.py                | 4 +---
 tests/unit/aleph_vm_authentication.py | 7 +++----
 3 files changed, 7 insertions(+), 13 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 56686c42..b81668f0 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,11 +5,10 @@
 from shutil import which
 from typing import ClassVar, Dict, Optional, Union
 
-from pydantic_settings import BaseSettings
-
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
 from pydantic import BaseModel, ConfigDict, Field
+from pydantic_settings import BaseSettings
 
 from aleph.sdk.types import ChainInfo
 
@@ -142,12 +141,10 @@ class Settings(BaseSettings):
     DNS_PROGRAM_DOMAIN: ClassVar[str] = "program.public.aleph.sh"
     DNS_INSTANCE_DOMAIN: ClassVar[str] = "instance.public.aleph.sh"
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
-    DNS_RESOLVERS: ClassVar[str] = ["9.9.9.9", "1.1.1.1"]
+    DNS_RESOLVERS: ClassVar[list[str]] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
-       env_prefix="ALEPH_",
-       case_sensitive=False,
-       env_file=".env"
+        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
     )
 
 
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index f9380c1a..f9ec05c5 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -28,15 +28,13 @@
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
 
-from pydantic import BaseModel
-
+import pydantic_core
 from aleph_message.models import ItemHash, MessageType
 from aleph_message.models.execution.program import Encoding
 from aleph_message.models.execution.volume import MachineVolume
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
-import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index 4c435482..a562a96d 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -13,7 +13,7 @@
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 from jwcrypto.jwa import JWA
-from pydantic import BaseModel, ValidationError, model_validator, field_validator
+from pydantic import BaseModel, ValidationError, field_validator, model_validator
 
 from aleph.sdk.utils import bytes_from_hex
 
@@ -74,7 +74,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values) -> Dict[str, bytes]:
+    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -86,8 +86,7 @@ def check_expiry(cls, values) -> Dict[str, bytes]:
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
-        """Check that the signature is valid"""
+    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         signature: bytes = values.signature
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)

From 213982bd17cfc032e2e10e3dc15451bc0b62eaef Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Mon, 21 Oct 2024 23:55:12 +0900
Subject: [PATCH 09/43] Fix: Project don't use the good version of
 aleph-message

There were changes made on aleph-message on the main branch about
pydantic version. Using the version by the url and then change it
later after the release.
---
 pyproject.toml | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 409694cf..165e1779 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,15 +30,16 @@ dynamic = [ "version" ]
 dependencies = [
   "aiohttp>=3.8.3",
   "aioresponses>=0.7.6",
-  "aleph-message>=0.4.9",
+  "aleph-message @ git+https://github.com/aleph-im/aleph-message#egg=main",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                         # Needed now as default with _load_account changement
+  "base58==2.1.1",                                                          # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
-  "pynacl==1.5",                           # Needed now as default with _load_account changement
+  "pydantic-settings>=2",
+  "pynacl==1.5",                                                            # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",

From 8894347629d72d16245cde5aa72e03fa2066e862 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 5 Nov 2024 23:00:54 +0900
Subject: [PATCH 10/43] fix: Wrong aleph-message version

---
 pyproject.toml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 165e1779..b313cac7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,16 +30,16 @@ dynamic = [ "version" ]
 dependencies = [
   "aiohttp>=3.8.3",
   "aioresponses>=0.7.6",
-  "aleph-message @ git+https://github.com/aleph-im/aleph-message#egg=main",
+  "aleph-message @ git+https://github.com/aleph-im/aleph-message@108-upgrade-pydantic-version#egg=aleph-message",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                                                          # Needed now as default with _load_account changement
+  "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",
-  "pynacl==1.5",                                                            # Needed now as default with _load_account changement
+  "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",

From 56743560a2e8f6db91d873771d66d5cf75cdfdc6 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 5 Nov 2024 23:18:34 +0900
Subject: [PATCH 11/43] Fix: list[str] rise an error in ubuntu 20.04

Using List from typing instead to assure the compatibility between python3.8 and above
---
 src/aleph/sdk/conf.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index b81668f0..36818cb5 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -3,7 +3,7 @@
 import os
 from pathlib import Path
 from shutil import which
-from typing import ClassVar, Dict, Optional, Union
+from typing import ClassVar, Dict, Optional, Union, List
 
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
@@ -141,7 +141,7 @@ class Settings(BaseSettings):
     DNS_PROGRAM_DOMAIN: ClassVar[str] = "program.public.aleph.sh"
     DNS_INSTANCE_DOMAIN: ClassVar[str] = "instance.public.aleph.sh"
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
-    DNS_RESOLVERS: ClassVar[list[str]] = ["9.9.9.9", "1.1.1.1"]
+    DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
         env_prefix="ALEPH_", case_sensitive=False, env_file=".env"

From 00177ae32327a126752cc8b91dcb1efd537e9fa7 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 5 Nov 2024 23:21:59 +0900
Subject: [PATCH 12/43] style: isort

---
 src/aleph/sdk/conf.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 36818cb5..bb40b3d8 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -3,7 +3,7 @@
 import os
 from pathlib import Path
 from shutil import which
-from typing import ClassVar, Dict, Optional, Union, List
+from typing import ClassVar, Dict, List, Optional, Union
 
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType

From afe055d4e960d7a1f510facf826174df98ca7618 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 29 Nov 2024 22:08:27 +0900
Subject: [PATCH 13/43] fix: Hugo comments

---
 src/aleph/sdk/client/http.py          | 3 ---
 tests/unit/aleph_vm_authentication.py | 6 ++++--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py
index a59531c6..60e65bb2 100644
--- a/src/aleph/sdk/client/http.py
+++ b/src/aleph/sdk/client/http.py
@@ -467,6 +467,3 @@ async def get_message_status(self, item_hash: str) -> MessageStatus:
             if resp.status == HTTPNotFound.status_code:
                 raise MessageNotFoundError(f"No such hash {item_hash}")
             resp.raise_for_status()
-
-        data = await resp.json()
-        return MessageStatus(**data)
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index a562a96d..ad796903 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -1,4 +1,6 @@
 # Keep datetime import as is as it allow patching in test
+from __future__ import annotations
+
 import datetime
 import functools
 import json
@@ -74,7 +76,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -86,7 +88,7 @@ def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_signature(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         signature: bytes = values.signature
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)

From 3e88f28cdebf0f2c3a173653ab0a803174693ab2 Mon Sep 17 00:00:00 2001
From: philogicae <38438271+philogicae@users.noreply.github.com>
Date: Wed, 4 Dec 2024 17:09:46 +0200
Subject: [PATCH 14/43] Add pydantic for better mypy tests + Fixes

---
 pyproject.toml                        | 1 +
 src/aleph/sdk/conf.py                 | 8 ++++----
 src/aleph/sdk/domain.py               | 4 ++--
 src/aleph/sdk/vm/cache.py             | 2 +-
 tests/unit/aleph_vm_authentication.py | 6 +++---
 tests/unit/test_price.py              | 2 +-
 6 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index b313cac7..dc0873d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -155,6 +155,7 @@ dependencies = [
   "ruff==0.4.8",
   "isort==5.13.2",
   "pyproject-fmt==2.2.1",
+  "pydantic-settings>=2",
 ]
 [tool.hatch.envs.linting.scripts]
 typing = "mypy --config-file=pyproject.toml {args:} ./src/ ./tests/ ./examples/"
diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 5035e29c..5a8fcc6c 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -7,8 +7,8 @@
 
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
-from pydantic import BaseModel, ConfigDict, Field
-from pydantic_settings import BaseSettings
+from pydantic import BaseModel, Field
+from pydantic_settings import BaseSettings, SettingsConfigDict
 
 from aleph.sdk.types import ChainInfo
 
@@ -224,7 +224,7 @@ class Settings(BaseSettings):
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
     DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
-    model_config = ConfigDict(
+    model_config = SettingsConfigDict(
         env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
     )
 
@@ -237,7 +237,7 @@ class MainConfiguration(BaseModel):
     path: Path
     chain: Chain
 
-    model_config = ConfigDict(use_enum_values=True)
+    model_config = SettingsConfigDict(use_enum_values=True)
 
 
 # Settings singleton
diff --git a/src/aleph/sdk/domain.py b/src/aleph/sdk/domain.py
index a8f3fd82..525e6cef 100644
--- a/src/aleph/sdk/domain.py
+++ b/src/aleph/sdk/domain.py
@@ -52,11 +52,11 @@ def raise_error(self, status: Dict[str, bool]):
 def hostname_from_url(url: Union[HttpUrl, str]) -> Hostname:
     """Extract FQDN from url"""
 
-    parsed = urlparse(url)
+    parsed = urlparse(str(url))
     if all([parsed.scheme, parsed.netloc]) is True:
         url = parsed.netloc
 
-    return Hostname(url)
+    return Hostname(str(url))
 
 
 async def get_target_type(fqdn: Hostname) -> Optional[TargetType]:
diff --git a/src/aleph/sdk/vm/cache.py b/src/aleph/sdk/vm/cache.py
index ff5ca7c8..a7ac6acc 100644
--- a/src/aleph/sdk/vm/cache.py
+++ b/src/aleph/sdk/vm/cache.py
@@ -70,7 +70,7 @@ def __init__(
             )
 
         self.cache = {}
-        self.api_host = connector_url if connector_url else settings.API_HOST
+        self.api_host = str(connector_url) if connector_url else settings.API_HOST
 
     async def get(self, key: str) -> Optional[bytes]:
         sanitized_key = sanitize_cache_key(key)
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index ad796903..c1710c16 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -75,7 +75,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the payload from hexadecimal to bytes"""
         return bytes_from_hex(value.decode())
 
-    @model_validator(mode="after")
+    @model_validator(mode="after")  # type: ignore
     def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         """Check that the token has not expired"""
         payload: bytes = values.payload
@@ -87,7 +87,7 @@ def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
 
         return values
 
-    @model_validator(mode="after")
+    @model_validator(mode="after")  # type: ignore
     def check_signature(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         signature: bytes = values.signature
         payload: bytes = values.payload
@@ -262,7 +262,7 @@ async def authenticate_websocket_message(
     signed_operation = SignedOperation.model_validate(message["X-SignedOperation"])
     if signed_operation.content.domain != domain_name:
         logger.debug(
-            f"Invalid domain '{signed_pubkey.content.domain}' != '{domain_name}'"
+            f"Invalid domain '{signed_operation.content.domain}' != '{domain_name}'"
         )
         raise web.HTTPUnauthorized(reason="Invalid domain")
     return verify_signed_operation(signed_operation, signed_pubkey)
diff --git a/tests/unit/test_price.py b/tests/unit/test_price.py
index bed9304a..b596c33b 100644
--- a/tests/unit/test_price.py
+++ b/tests/unit/test_price.py
@@ -18,7 +18,7 @@ async def test_get_program_price_valid():
     mock_session = make_mock_get_session(expected_response)
     async with mock_session:
         response = await mock_session.get_program_price("cacacacacacaca")
-        assert response == PriceResponse(**expected_response)
+        assert response == PriceResponse(**expected_response)  # type: ignore
 
 
 @pytest.mark.asyncio

From c7d3a88f96782b405dfbd4bfc5dd4832881574a5 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 15 Jan 2025 11:24:32 +0900
Subject: [PATCH 15/43] fix: Changing version of aleph-message

---
 pyproject.toml | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 635d3aa1..95ed9d4c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,16 +30,16 @@ dynamic = [ "version" ]
 dependencies = [
   "aiohttp>=3.8.3",
   "aioresponses>=0.7.6",
-  "aleph-message>=0.6",
+  "aleph-message @ git+https://github.com/aleph-im/aleph-message@108-upgrade-pydantic-version#egg=aleph-message",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
+  "base58==2.1.1",                         # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",
-  "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
+  "pynacl==1.5",                           # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",
@@ -134,6 +134,7 @@ dependencies = [
   "httpx",
   "secp256k1",
 ]
+
 [tool.hatch.envs.testing.scripts]
 test = "pytest {args:} ./src/ ./tests/ ./examples/"
 test-cov = "pytest --cov {args:} ./src/ ./tests/ ./examples/"

From f9bdd3c2b98984150296dcf0fc11e563a4f31b27 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 15 Jan 2025 11:27:02 +0900
Subject: [PATCH 16/43] style: Missing type for URL

---
 src/aleph/sdk/conf.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index b6154662..16ddc4a1 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -84,9 +84,9 @@ class Settings(BaseSettings):
 
     CODE_USES_SQUASHFS: bool = which("mksquashfs") is not None  # True if command exists
 
-    VM_URL_PATH = "https://aleph.sh/vm/{hash}"
-    VM_URL_HOST = "https://{hash_base32}.aleph.sh"
-    IPFS_GATEWAY = "https://ipfs.aleph.cloud/ipfs/"
+    VM_URL_PATH: ClassVar[str] = "https://aleph.sh/vm/{hash}"
+    VM_URL_HOST: ClassVar[str] = "https://{hash_base32}.aleph.sh"
+    IPFS_GATEWAY: ClassVar[str] = "https://ipfs.aleph.cloud/ipfs/"
 
     # Web3Provider settings
     TOKEN_DECIMALS: ClassVar[int] = 18

From d1e4e518a8fc81027be2a4101bb3fd7aa4a82944 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 15 Jan 2025 11:27:22 +0900
Subject: [PATCH 17/43] style: Missing type for URL

---
 pyproject.toml | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 95ed9d4c..f97fd0a1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,14 +32,14 @@ dependencies = [
   "aioresponses>=0.7.6",
   "aleph-message @ git+https://github.com/aleph-im/aleph-message@108-upgrade-pydantic-version#egg=aleph-message",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                         # Needed now as default with _load_account changement
+  "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",
-  "pynacl==1.5",                           # Needed now as default with _load_account changement
+  "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",
@@ -134,7 +134,6 @@ dependencies = [
   "httpx",
   "secp256k1",
 ]
-
 [tool.hatch.envs.testing.scripts]
 test = "pytest {args:} ./src/ ./tests/ ./examples/"
 test-cov = "pytest --cov {args:} ./src/ ./tests/ ./examples/"

From 2c68cd347e298302b02eb46a43fc28e06bfae470 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 21 Jan 2025 20:11:24 +0900
Subject: [PATCH 18/43] fix: Changing version of aleph-message and fix mypy

Changing the version from the branch to the main of aleph-message
mypy rose some errors about missing name argument, so setting the as None because
they are optional
---
 pyproject.toml         |  6 +++---
 src/aleph/sdk/types.py | 10 +++++-----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index f97fd0a1..f7911982 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,16 +30,16 @@ dynamic = [ "version" ]
 dependencies = [
   "aiohttp>=3.8.3",
   "aioresponses>=0.7.6",
-  "aleph-message @ git+https://github.com/aleph-im/aleph-message@108-upgrade-pydantic-version#egg=aleph-message",
+  "aleph-message>=0.6",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
+  "base58==2.1.1",                         # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",
-  "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
+  "pynacl==1.5",                           # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",
diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py
index c698da5d..e192335f 100644
--- a/src/aleph/sdk/types.py
+++ b/src/aleph/sdk/types.py
@@ -2,7 +2,7 @@
 from enum import Enum
 from typing import Dict, Optional, Protocol, TypeVar
 
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 
 __all__ = ("StorageEnum", "Account", "AccountFromPrivateKey", "GenericMessage")
 
@@ -83,7 +83,7 @@ class ChainInfo(BaseModel):
 
 
 class StoredContent(BaseModel):
-    filename: Optional[str]
-    hash: Optional[str]
-    url: Optional[str]
-    error: Optional[str]
+    filename: Optional[str] = Field(default=None)
+    hash: Optional[str] = Field(default=None)
+    url: Optional[str] = Field(default=None)
+    error: Optional[str] = Field(default=None)

From ac1b48ffd92bde39a3976a6137220c3f9374152f Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 21 Jan 2025 22:13:24 +0900
Subject: [PATCH 19/43] fix: Changing version of aleph-message

---
 pyproject.toml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index f7911982..dc0873d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,16 +30,16 @@ dynamic = [ "version" ]
 dependencies = [
   "aiohttp>=3.8.3",
   "aioresponses>=0.7.6",
-  "aleph-message>=0.6",
+  "aleph-message @ git+https://github.com/aleph-im/aleph-message@108-upgrade-pydantic-version#egg=aleph-message",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                         # Needed now as default with _load_account changement
+  "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",
-  "pynacl==1.5",                           # Needed now as default with _load_account changement
+  "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",
@@ -62,7 +62,7 @@ optional-dependencies.encryption = [
   "eciespy>=0.3.13; python_version>='3.11'",
 ]
 optional-dependencies.ledger = [
-  "ledgereth==0.10",
+  "ledgereth==0.9.1",
 ]
 optional-dependencies.mqtt = [
   "aiomqtt<=0.1.3",
@@ -81,7 +81,7 @@ optional-dependencies.substrate = [
   "substrate-interface",
 ]
 optional-dependencies.tezos = [
-  "aleph-pytezos==3.13.4",
+  "aleph-pytezos==0.1.1",
   "pynacl",
 ]
 urls.Documentation = "https://aleph.im/"

From 5ce940ef250cf5cb8ed367b045e1216e1d8e1ec5 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 21 Jan 2025 22:25:11 +0900
Subject: [PATCH 20/43] fix: Changing version of pytezos

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index dc0873d8..bc28fb15 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -81,7 +81,7 @@ optional-dependencies.substrate = [
   "substrate-interface",
 ]
 optional-dependencies.tezos = [
-  "aleph-pytezos==0.1.1",
+  "aleph-pytezos==3.13.4",
   "pynacl",
 ]
 urls.Documentation = "https://aleph.im/"

From 7c942c7002742c4c0190c62a90cd0ad574923e6e Mon Sep 17 00:00:00 2001
From: philogicae <philogicae+github@gmail.com>
Date: Tue, 18 Feb 2025 19:05:00 +0200
Subject: [PATCH 21/43] Changes for new pricing system (#199)

- Move/improve flow code parts from CLI to SDK
- Add utils functions
- Add `make_instance_content` and `make_program_content`
- Refactor `create_instance` and `create_program`
- Add `get_estimated_price`
- Fixes for mypy/ruff/pytest
- Minor improvements
- Remove firecracker rootfs hashes for instances
---
 src/aleph/sdk/chains/ethereum.py           |  26 ++-
 src/aleph/sdk/chains/evm.py                |   9 +
 src/aleph/sdk/client/abstract.py           |  80 ++++---
 src/aleph/sdk/client/authenticated_http.py | 239 +++++++++------------
 src/aleph/sdk/client/http.py               |  77 ++++++-
 src/aleph/sdk/conf.py                      |  20 +-
 src/aleph/sdk/connectors/superfluid.py     |  65 +++++-
 src/aleph/sdk/evm_utils.py                 |  23 +-
 src/aleph/sdk/exceptions.py                |  11 +-
 src/aleph/sdk/types.py                     |  13 ++
 src/aleph/sdk/utils.py                     | 213 +++++++++++++++++-
 tests/unit/test_asynchronous.py            |  10 +-
 tests/unit/test_price.py                   |  10 +-
 tests/unit/test_superfluid.py              |  13 ++
 tests/unit/test_utils.py                   |  10 +-
 15 files changed, 588 insertions(+), 231 deletions(-)

diff --git a/src/aleph/sdk/chains/ethereum.py b/src/aleph/sdk/chains/ethereum.py
index ab93df56..c185d174 100644
--- a/src/aleph/sdk/chains/ethereum.py
+++ b/src/aleph/sdk/chains/ethereum.py
@@ -15,6 +15,7 @@
 from web3.types import TxParams, TxReceipt
 
 from aleph.sdk.exceptions import InsufficientFundsError
+from aleph.sdk.types import TokenType
 
 from ..conf import settings
 from ..connectors.superfluid import Superfluid
@@ -22,12 +23,13 @@
     BALANCEOF_ABI,
     MIN_ETH_BALANCE,
     MIN_ETH_BALANCE_WEI,
+    FlowUpdate,
+    from_wei_token,
     get_chain_id,
     get_chains_with_super_token,
     get_rpc,
     get_super_token_address,
     get_token_address,
-    to_human_readable_token,
 )
 from ..exceptions import BadSignatureError
 from ..utils import bytes_from_hex
@@ -106,8 +108,9 @@ def can_transact(self, block=True) -> bool:
         valid = balance > MIN_ETH_BALANCE_WEI if self.chain else False
         if not valid and block:
             raise InsufficientFundsError(
+                token_type=TokenType.GAS,
                 required_funds=MIN_ETH_BALANCE,
-                available_funds=to_human_readable_token(balance),
+                available_funds=float(from_wei_token(balance)),
             )
         return valid
 
@@ -162,6 +165,12 @@ def get_super_token_balance(self) -> Decimal:
                 return Decimal(contract.functions.balanceOf(self.get_address()).call())
         return Decimal(0)
 
+    def can_start_flow(self, flow: Decimal) -> bool:
+        """Check if the account has enough funds to start a Superfluid flow of the given size."""
+        if not self.superfluid_connector:
+            raise ValueError("Superfluid connector is required to check a flow")
+        return self.superfluid_connector.can_start_flow(flow)
+
     def create_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
         """Creat a Superfluid flow between this account and the receiver address."""
         if not self.superfluid_connector:
@@ -188,6 +197,19 @@ def delete_flow(self, receiver: str) -> Awaitable[str]:
             raise ValueError("Superfluid connector is required to delete a flow")
         return self.superfluid_connector.delete_flow(receiver=receiver)
 
+    def manage_flow(
+        self,
+        receiver: str,
+        flow: Decimal,
+        update_type: FlowUpdate,
+    ) -> Awaitable[Optional[str]]:
+        """Manage the Superfluid flow between this account and the receiver address."""
+        if not self.superfluid_connector:
+            raise ValueError("Superfluid connector is required to manage a flow")
+        return self.superfluid_connector.manage_flow(
+            receiver=receiver, flow=flow, update_type=update_type
+        )
+
 
 def get_fallback_account(
     path: Optional[Path] = None, chain: Optional[Chain] = None
diff --git a/src/aleph/sdk/chains/evm.py b/src/aleph/sdk/chains/evm.py
index 5bf66ef1..a5eeed84 100644
--- a/src/aleph/sdk/chains/evm.py
+++ b/src/aleph/sdk/chains/evm.py
@@ -5,6 +5,7 @@
 from aleph_message.models import Chain
 from eth_account import Account  # type: ignore
 
+from ..evm_utils import FlowUpdate
 from .common import get_fallback_private_key
 from .ethereum import ETHAccount
 
@@ -29,6 +30,9 @@ def get_token_balance(self) -> Decimal:
     def get_super_token_balance(self) -> Decimal:
         raise ValueError(f"Super token not implemented for this chain {self.CHAIN}")
 
+    def can_start_flow(self, flow: Decimal) -> bool:
+        raise ValueError(f"Flow checking not implemented for this chain {self.CHAIN}")
+
     def create_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
         raise ValueError(f"Flow creation not implemented for this chain {self.CHAIN}")
 
@@ -41,6 +45,11 @@ def update_flow(self, receiver: str, flow: Decimal) -> Awaitable[str]:
     def delete_flow(self, receiver: str) -> Awaitable[str]:
         raise ValueError(f"Flow deletion not implemented for this chain {self.CHAIN}")
 
+    def manage_flow(
+        self, receiver: str, flow: Decimal, update_type: FlowUpdate
+    ) -> Awaitable[Optional[str]]:
+        raise ValueError(f"Flow management not implemented for this chain {self.CHAIN}")
+
 
 def get_fallback_account(
     path: Optional[Path] = None, chain: Optional[Chain] = None
diff --git a/src/aleph/sdk/client/abstract.py b/src/aleph/sdk/client/abstract.py
index 025aae6a..7f9fed8e 100644
--- a/src/aleph/sdk/client/abstract.py
+++ b/src/aleph/sdk/client/abstract.py
@@ -20,9 +20,9 @@
 
 from aleph_message.models import (
     AlephMessage,
+    ExecutableContent,
     ItemHash,
     ItemType,
-    MessagesResponse,
     MessageType,
     Payment,
     PostMessage,
@@ -41,7 +41,7 @@
 from aleph.sdk.utils import extended_json_encoder
 
 from ..query.filters import MessageFilter, PostFilter
-from ..query.responses import PostsResponse, PriceResponse
+from ..query.responses import MessagesResponse, PostsResponse, PriceResponse
 from ..types import GenericMessage, StorageEnum
 from ..utils import Writable, compute_sha256
 
@@ -110,7 +110,7 @@ async def get_posts_iterator(
             )
             page += 1
             for post in resp.posts:
-                yield post
+                yield post  # type: ignore
 
     @abstractmethod
     async def download_file(self, file_hash: str) -> bytes:
@@ -242,6 +242,18 @@ def watch_messages(
         """
         raise NotImplementedError("Did you mean to import `AlephHttpClient`?")
 
+    @abstractmethod
+    def get_estimated_price(
+        self,
+        content: ExecutableContent,
+    ) -> Coroutine[Any, Any, PriceResponse]:
+        """
+        Get Instance/Program content estimated price
+
+        :param content: Instance or Program content
+        """
+        raise NotImplementedError("Did you mean to import `AlephHttpClient`?")
+
     @abstractmethod
     def get_program_price(
         self,
@@ -265,7 +277,7 @@ async def create_post(
         post_type: str,
         ref: Optional[str] = None,
         address: Optional[str] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         inline: bool = True,
         storage_engine: StorageEnum = StorageEnum.storage,
         sync: bool = False,
@@ -290,9 +302,9 @@ async def create_post(
     async def create_aggregate(
         self,
         key: str,
-        content: Mapping[str, Any],
+        content: dict[str, Any],
         address: Optional[str] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         inline: bool = True,
         sync: bool = False,
     ) -> Tuple[AlephMessage, MessageStatus]:
@@ -302,7 +314,7 @@ async def create_aggregate(
         :param key: Key to use to store the content
         :param content: Content to store
         :param address: Address to use to sign the message
-        :param channel: Channel to use (Default: "TEST")
+        :param channel: Channel to use (Default: "ALEPH-CLOUDSOLUTIONS")
         :param inline: Whether to write content inside the message (Default: True)
         :param sync: If true, waits for the message to be processed by the API server (Default: False)
         """
@@ -321,7 +333,7 @@ async def create_store(
         ref: Optional[str] = None,
         storage_engine: StorageEnum = StorageEnum.storage,
         extra_fields: Optional[dict] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         sync: bool = False,
     ) -> Tuple[AlephMessage, MessageStatus]:
         """
@@ -350,22 +362,22 @@ async def create_program(
         program_ref: str,
         entrypoint: str,
         runtime: str,
-        environment_variables: Optional[Mapping[str, str]] = None,
-        storage_engine: StorageEnum = StorageEnum.storage,
-        channel: Optional[str] = None,
+        metadata: Optional[dict[str, Any]] = None,
         address: Optional[str] = None,
-        sync: bool = False,
-        memory: Optional[int] = None,
         vcpus: Optional[int] = None,
+        memory: Optional[int] = None,
         timeout_seconds: Optional[float] = None,
-        persistent: bool = False,
-        allow_amend: bool = False,
         internet: bool = True,
+        allow_amend: bool = False,
         aleph_api: bool = True,
         encoding: Encoding = Encoding.zip,
+        persistent: bool = False,
         volumes: Optional[List[Mapping]] = None,
-        subscriptions: Optional[List[Mapping]] = None,
-        metadata: Optional[Mapping[str, Any]] = None,
+        environment_variables: Optional[dict[str, str]] = None,
+        subscriptions: Optional[List[dict]] = None,
+        sync: bool = False,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
+        storage_engine: StorageEnum = StorageEnum.storage,
     ) -> Tuple[AlephMessage, MessageStatus]:
         """
         Post a (create) PROGRAM message.
@@ -373,22 +385,22 @@ async def create_program(
         :param program_ref: Reference to the program to run
         :param entrypoint: Entrypoint to run
         :param runtime: Runtime to use
-        :param environment_variables: Environment variables to pass to the program
-        :param storage_engine: Storage engine to use (Default: "storage")
-        :param channel: Channel to use (Default: "TEST")
+        :param metadata: Metadata to attach to the message
         :param address: Address to use (Default: account.get_address())
-        :param sync: If true, waits for the message to be processed by the API server
-        :param memory: Memory in MB for the VM to be allocated (Default: 128)
         :param vcpus: Number of vCPUs to allocate (Default: 1)
+        :param memory: Memory in MB for the VM to be allocated (Default: 128)
         :param timeout_seconds: Timeout in seconds (Default: 30.0)
-        :param persistent: Whether the program should be persistent or not (Default: False)
-        :param allow_amend: Whether the deployed VM image may be changed (Default: False)
         :param internet: Whether the VM should have internet connectivity. (Default: True)
+        :param allow_amend: Whether the deployed VM image may be changed (Default: False)
         :param aleph_api: Whether the VM needs access to Aleph messages API (Default: True)
         :param encoding: Encoding to use (Default: Encoding.zip)
+        :param persistent: Whether the program should be persistent or not (Default: False)
         :param volumes: Volumes to mount
+        :param environment_variables: Environment variables to pass to the program
         :param subscriptions: Patterns of aleph.im messages to forward to the program's event receiver
-        :param metadata: Metadata to attach to the message
+        :param sync: If true, waits for the message to be processed by the API server
+        :param channel: Channel to use (Default: "ALEPH-CLOUDSOLUTIONS")
+        :param storage_engine: Storage engine to use (Default: "storage")
         """
         raise NotImplementedError(
             "Did you mean to import `AuthenticatedAlephHttpClient`?"
@@ -400,9 +412,9 @@ async def create_instance(
         rootfs: str,
         rootfs_size: int,
         payment: Optional[Payment] = None,
-        environment_variables: Optional[Mapping[str, str]] = None,
+        environment_variables: Optional[dict[str, str]] = None,
         storage_engine: StorageEnum = StorageEnum.storage,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         address: Optional[str] = None,
         sync: bool = False,
         memory: Optional[int] = None,
@@ -416,7 +428,7 @@ async def create_instance(
         volumes: Optional[List[Mapping]] = None,
         volume_persistence: str = "host",
         ssh_keys: Optional[List[str]] = None,
-        metadata: Optional[Mapping[str, Any]] = None,
+        metadata: Optional[dict[str, Any]] = None,
         requirements: Optional[HostRequirements] = None,
     ) -> Tuple[AlephMessage, MessageStatus]:
         """
@@ -427,7 +439,7 @@ async def create_instance(
         :param payment: Payment method used to pay for the instance
         :param environment_variables: Environment variables to pass to the program
         :param storage_engine: Storage engine to use (Default: "storage")
-        :param channel: Channel to use (Default: "TEST")
+        :param channel: Channel to use (Default: "ALEPH-CLOUDSOLUTIONS")
         :param address: Address to use (Default: account.get_address())
         :param sync: If true, waits for the message to be processed by the API server
         :param memory: Memory in MB for the VM to be allocated (Default: 2048)
@@ -455,7 +467,7 @@ async def forget(
         hashes: List[ItemHash],
         reason: Optional[str],
         storage_engine: StorageEnum = StorageEnum.storage,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         address: Optional[str] = None,
         sync: bool = False,
     ) -> Tuple[AlephMessage, MessageStatus]:
@@ -468,7 +480,7 @@ async def forget(
         :param hashes: Hashes of the messages to forget
         :param reason: Reason for forgetting the messages
         :param storage_engine: Storage engine to use (Default: "storage")
-        :param channel: Channel to use (Default: "TEST")
+        :param channel: Channel to use (Default: "ALEPH-CLOUDSOLUTIONS")
         :param address: Address to use (Default: account.get_address())
         :param sync: If true, waits for the message to be processed by the API server (Default: False)
         """
@@ -490,7 +502,7 @@ async def generate_signed_message(
 
         :param message_type: Type of the message (PostMessage, ...)
         :param content: User-defined content of the message
-        :param channel: Channel to use (Default: "TEST")
+        :param channel: Channel to use (Default: "ALEPH-CLOUDSOLUTIONS")
         :param allow_inlining: Whether to allow inlining the content of the message (Default: True)
         :param storage_engine: Storage engine to use (Default: "storage")
         """
@@ -537,7 +549,7 @@ async def submit(
         self,
         content: Dict[str, Any],
         message_type: MessageType,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         storage_engine: StorageEnum = StorageEnum.storage,
         allow_inlining: bool = True,
         sync: bool = False,
@@ -549,7 +561,7 @@ async def submit(
 
         :param content: Content of the message
         :param message_type: Type of the message
-        :param channel: Channel to use (Default: "TEST")
+        :param channel: Channel to use (Default: "ALEPH-CLOUDSOLUTIONS")
         :param storage_engine: Storage engine to use (Default: "storage")
         :param allow_inlining: Whether to allow inlining the content of the message (Default: True)
         :param sync: If true, waits for the message to be processed by the API server (Default: False)
diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py
index ad15ca09..f544a9c6 100644
--- a/src/aleph/sdk/client/authenticated_http.py
+++ b/src/aleph/sdk/client/authenticated_http.py
@@ -5,45 +5,37 @@
 import time
 from io import BytesIO
 from pathlib import Path
-from typing import Any, Dict, List, Mapping, NoReturn, Optional, Tuple, Union
+from typing import Any, Dict, Mapping, NoReturn, Optional, Tuple, Union
 
 import aiohttp
 from aleph_message.models import (
     AggregateContent,
     AggregateMessage,
     AlephMessage,
-    Chain,
     ForgetContent,
     ForgetMessage,
-    InstanceContent,
     InstanceMessage,
     ItemHash,
+    ItemType,
     MessageType,
     PostContent,
     PostMessage,
-    ProgramContent,
     ProgramMessage,
     StoreContent,
     StoreMessage,
 )
-from aleph_message.models.execution.base import Encoding, Payment, PaymentType
+from aleph_message.models.execution.base import Encoding, Payment
 from aleph_message.models.execution.environment import (
-    FunctionEnvironment,
     HostRequirements,
     HypervisorType,
-    InstanceEnvironment,
-    MachineResources,
     TrustedExecutionEnvironment,
 )
-from aleph_message.models.execution.instance import RootfsVolume
-from aleph_message.models.execution.program import CodeContent, FunctionRuntime
-from aleph_message.models.execution.volume import MachineVolume, ParentVolume
 from aleph_message.status import MessageStatus
 
 from ..conf import settings
 from ..exceptions import BroadcastError, InsufficientFundsError, InvalidMessageError
-from ..types import Account, StorageEnum
-from ..utils import extended_json_encoder, parse_volume
+from ..types import Account, StorageEnum, TokenType
+from ..utils import extended_json_encoder, make_instance_content, make_program_content
 from .abstract import AuthenticatedAlephClient
 from .http import AlephHttpClient
 
@@ -285,7 +277,7 @@ async def create_post(
         post_type: str,
         ref: Optional[str] = None,
         address: Optional[str] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         inline: bool = True,
         storage_engine: StorageEnum = StorageEnum.storage,
         sync: bool = False,
@@ -308,14 +300,14 @@ async def create_post(
             storage_engine=storage_engine,
             sync=sync,
         )
-        return message, status
+        return message, status  # type: ignore
 
     async def create_aggregate(
         self,
         key: str,
-        content: Mapping[str, Any],
+        content: dict[str, Any],
         address: Optional[str] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         inline: bool = True,
         sync: bool = False,
     ) -> Tuple[AggregateMessage, MessageStatus]:
@@ -335,7 +327,7 @@ async def create_aggregate(
             allow_inlining=inline,
             sync=sync,
         )
-        return message, status
+        return message, status  # type: ignore
 
     async def create_store(
         self,
@@ -347,7 +339,7 @@ async def create_store(
         ref: Optional[str] = None,
         storage_engine: StorageEnum = StorageEnum.storage,
         extra_fields: Optional[dict] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         sync: bool = False,
     ) -> Tuple[StoreMessage, MessageStatus]:
         address = address or settings.ADDRESS_TO_USE or self.account.get_address()
@@ -400,7 +392,7 @@ async def create_store(
         if extra_fields is not None:
             values.update(extra_fields)
 
-        content = StoreContent(**values)
+        content = StoreContent.parse_obj(values)
 
         message, status, _ = await self.submit(
             content=content.model_dump(exclude_none=True),
@@ -409,109 +401,89 @@ async def create_store(
             allow_inlining=True,
             sync=sync,
         )
-        return message, status
+        return message, status  # type: ignore
 
     async def create_program(
         self,
         program_ref: str,
         entrypoint: str,
         runtime: str,
-        environment_variables: Optional[Mapping[str, str]] = None,
-        storage_engine: StorageEnum = StorageEnum.storage,
-        channel: Optional[str] = None,
+        metadata: Optional[dict[str, Any]] = None,
         address: Optional[str] = None,
-        sync: bool = False,
-        memory: Optional[int] = None,
         vcpus: Optional[int] = None,
+        memory: Optional[int] = None,
         timeout_seconds: Optional[float] = None,
-        persistent: bool = False,
-        allow_amend: bool = False,
         internet: bool = True,
+        allow_amend: bool = False,
         aleph_api: bool = True,
         encoding: Encoding = Encoding.zip,
-        volumes: Optional[List[Mapping]] = None,
-        subscriptions: Optional[List[Mapping]] = None,
-        metadata: Optional[Mapping[str, Any]] = None,
+        persistent: bool = False,
+        volumes: Optional[list[Mapping]] = None,
+        environment_variables: Optional[dict[str, str]] = None,
+        subscriptions: Optional[list[dict]] = None,
+        sync: bool = False,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
+        storage_engine: StorageEnum = StorageEnum.storage,
     ) -> Tuple[ProgramMessage, MessageStatus]:
         address = address or settings.ADDRESS_TO_USE or self.account.get_address()
 
-        volumes = volumes if volumes is not None else []
-        memory = memory or settings.DEFAULT_VM_MEMORY
-        vcpus = vcpus or settings.DEFAULT_VM_VCPUS
-        timeout_seconds = timeout_seconds or settings.DEFAULT_VM_TIMEOUT
-
-        # TODO: Check that program_ref, runtime and data_ref exist
-
-        # Register the different ways to trigger a VM
-        if subscriptions:
-            # Trigger on HTTP calls and on aleph.im message subscriptions.
-            triggers = {
-                "http": True,
-                "persistent": persistent,
-                "message": subscriptions,
-            }
-        else:
-            # Trigger on HTTP calls.
-            triggers = {"http": True, "persistent": persistent}
-
-        volumes: List[MachineVolume] = [parse_volume(volume) for volume in volumes]
-
-        content = ProgramContent(
-            type="vm-function",
+        content = make_program_content(
+            program_ref=program_ref,
+            entrypoint=entrypoint,
+            runtime=runtime,
+            metadata=metadata,
             address=address,
+            vcpus=vcpus,
+            memory=memory,
+            timeout_seconds=timeout_seconds,
+            internet=internet,
+            aleph_api=aleph_api,
             allow_amend=allow_amend,
-            code=CodeContent(
-                encoding=encoding,
-                entrypoint=entrypoint,
-                ref=program_ref,
-                use_latest=True,
-            ),
-            on=triggers,
-            environment=FunctionEnvironment(
-                reproducible=False,
-                internet=internet,
-                aleph_api=aleph_api,
-            ),
-            variables=environment_variables,
-            resources=MachineResources(
-                vcpus=vcpus,
-                memory=memory,
-                seconds=timeout_seconds,
-            ),
-            runtime=FunctionRuntime(
-                ref=runtime,
-                use_latest=True,
-                comment=(
-                    "Official aleph.im runtime"
-                    if runtime == settings.DEFAULT_RUNTIME_ID
-                    else ""
-                ),
-            ),
-            volumes=[parse_volume(volume) for volume in volumes],
-            time=time.time(),
-            metadata=metadata,
+            encoding=encoding,
+            persistent=persistent,
+            volumes=volumes,
+            environment_variables=environment_variables,
+            subscriptions=subscriptions,
         )
 
-        # Ensure that the version of aleph-message used supports the field.
-        assert content.on.persistent == persistent
-
         message, status, _ = await self.submit(
             content=content.model_dump(exclude_none=True),
             message_type=MessageType.program,
             channel=channel,
             storage_engine=storage_engine,
             sync=sync,
+            raise_on_rejected=False,
         )
-        return message, status
+        if status in (MessageStatus.PROCESSED, MessageStatus.PENDING):
+            return message, status  # type: ignore
+
+        # get the reason for rejection
+        rejected_message = await self.get_message_error(message.item_hash)
+        assert rejected_message, "No rejected message found"
+        error_code = rejected_message["error_code"]
+        if error_code == 5:
+            # not enough balance
+            details = rejected_message["details"]
+            errors = details["errors"]
+            error = errors[0]
+            account_balance = float(error["account_balance"])
+            required_balance = float(error["required_balance"])
+            raise InsufficientFundsError(
+                token_type=TokenType.ALEPH,
+                required_funds=required_balance,
+                available_funds=account_balance,
+            )
+        else:
+            raise ValueError(f"Unknown error code {error_code}: {rejected_message}")
 
     async def create_instance(
         self,
         rootfs: str,
         rootfs_size: int,
         payment: Optional[Payment] = None,
-        environment_variables: Optional[Mapping[str, str]] = None,
+        environment_variables: Optional[dict[str, str]] = None,
         storage_engine: StorageEnum = StorageEnum.storage,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         address: Optional[str] = None,
         sync: bool = False,
         memory: Optional[int] = None,
@@ -522,57 +494,36 @@ async def create_instance(
         aleph_api: bool = True,
         hypervisor: Optional[HypervisorType] = None,
         trusted_execution: Optional[TrustedExecutionEnvironment] = None,
-        volumes: Optional[List[Mapping]] = None,
+        volumes: Optional[list[Mapping]] = None,
         volume_persistence: str = "host",
-        ssh_keys: Optional[List[str]] = None,
-        metadata: Optional[Mapping[str, Any]] = None,
+        ssh_keys: Optional[list[str]] = None,
+        metadata: Optional[dict[str, Any]] = None,
         requirements: Optional[HostRequirements] = None,
     ) -> Tuple[InstanceMessage, MessageStatus]:
         address = address or settings.ADDRESS_TO_USE or self.account.get_address()
 
-        volumes = volumes if volumes is not None else []
-        memory = memory or settings.DEFAULT_VM_MEMORY
-        vcpus = vcpus or settings.DEFAULT_VM_VCPUS
-        timeout_seconds = timeout_seconds or settings.DEFAULT_VM_TIMEOUT
-
-        payment = payment or Payment(chain=Chain.ETH, type=PaymentType.hold)
-
-        # Default to the QEMU hypervisor for instances.
-        selected_hypervisor: HypervisorType = hypervisor or HypervisorType.qemu
-
-        content = InstanceContent(
+        content = make_instance_content(
+            rootfs=rootfs,
+            rootfs_size=rootfs_size,
+            payment=payment,
+            environment_variables=environment_variables,
             address=address,
+            memory=memory,
+            vcpus=vcpus,
+            timeout_seconds=timeout_seconds,
             allow_amend=allow_amend,
-            environment=InstanceEnvironment(
-                internet=internet,
-                aleph_api=aleph_api,
-                hypervisor=selected_hypervisor,
-                trusted_execution=trusted_execution,
-            ),
-            variables=environment_variables,
-            resources=MachineResources(
-                vcpus=vcpus,
-                memory=memory,
-                seconds=timeout_seconds,
-            ),
-            rootfs=RootfsVolume(
-                parent=ParentVolume(
-                    ref=rootfs,
-                    use_latest=True,
-                ),
-                size_mib=rootfs_size,
-                persistence="host",
-                use_latest=True,
-            ),
-            volumes=[parse_volume(volume) for volume in volumes],
-            requirements=requirements,
-            time=time.time(),
-            authorized_keys=ssh_keys,
+            internet=internet,
+            aleph_api=aleph_api,
+            hypervisor=hypervisor,
+            trusted_execution=trusted_execution,
+            volumes=volumes,
+            ssh_keys=ssh_keys,
             metadata=metadata,
-            payment=payment,
+            requirements=requirements,
         )
+
         message, status, response = await self.submit(
-            content=content.model_dump(exclude_none=True),
+            content=content.dict(exclude_none=True),
             message_type=MessageType.instance,
             channel=channel,
             storage_engine=storage_engine,
@@ -580,7 +531,7 @@ async def create_instance(
             raise_on_rejected=False,
         )
         if status in (MessageStatus.PROCESSED, MessageStatus.PENDING):
-            return message, status
+            return message, status  # type: ignore
 
         # get the reason for rejection
         rejected_message = await self.get_message_error(message.item_hash)
@@ -594,17 +545,19 @@ async def create_instance(
             account_balance = float(error["account_balance"])
             required_balance = float(error["required_balance"])
             raise InsufficientFundsError(
-                required_funds=required_balance, available_funds=account_balance
+                token_type=TokenType.ALEPH,
+                required_funds=required_balance,
+                available_funds=account_balance,
             )
         else:
             raise ValueError(f"Unknown error code {error_code}: {rejected_message}")
 
     async def forget(
         self,
-        hashes: List[ItemHash],
+        hashes: list[ItemHash],
         reason: Optional[str],
         storage_engine: StorageEnum = StorageEnum.storage,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         address: Optional[str] = None,
         sync: bool = False,
     ) -> Tuple[ForgetMessage, MessageStatus]:
@@ -625,13 +578,13 @@ async def forget(
             allow_inlining=True,
             sync=sync,
         )
-        return message, status
+        return message, status  # type: ignore
 
     async def submit(
         self,
         content: Dict[str, Any],
         message_type: MessageType,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         storage_engine: StorageEnum = StorageEnum.storage,
         allow_inlining: bool = True,
         sync: bool = False,
@@ -653,7 +606,7 @@ async def _storage_push_file_with_message(
         self,
         file_content: bytes,
         store_content: StoreContent,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         sync: bool = False,
     ) -> Tuple[StoreMessage, MessageStatus]:
         """Push a file to the storage service."""
@@ -685,7 +638,7 @@ async def _storage_push_file_with_message(
             message_status = (
                 MessageStatus.PENDING if resp.status == 202 else MessageStatus.PROCESSED
             )
-            return message, message_status
+            return message, message_status  # type: ignore
 
     async def _upload_file_native(
         self,
@@ -694,7 +647,7 @@ async def _upload_file_native(
         guess_mime_type: bool = False,
         ref: Optional[str] = None,
         extra_fields: Optional[dict] = None,
-        channel: Optional[str] = None,
+        channel: Optional[str] = settings.DEFAULT_CHANNEL,
         sync: bool = False,
     ) -> Tuple[StoreMessage, MessageStatus]:
         file_hash = hashlib.sha256(file_content).hexdigest()
@@ -706,9 +659,9 @@ async def _upload_file_native(
         store_content = StoreContent(
             address=address,
             ref=ref,
-            item_type=StorageEnum.storage,
-            item_hash=file_hash,
-            mime_type=mime_type,
+            item_type=ItemType.storage,
+            item_hash=ItemHash(file_hash),
+            mime_type=mime_type,  # type: ignore
             time=time.time(),
             **(extra_fields or {}),
         )
diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py
index ef57033c..6d2b42c7 100644
--- a/src/aleph/sdk/client/http.py
+++ b/src/aleph/sdk/client/http.py
@@ -2,6 +2,7 @@
 import logging
 import os.path
 import ssl
+import time
 from io import BytesIO
 from pathlib import Path
 from typing import (
@@ -20,7 +21,15 @@
 import aiohttp
 from aiohttp.web import HTTPNotFound
 from aleph_message import parse_message
-from aleph_message.models import AlephMessage, ItemHash, ItemType, MessageType
+from aleph_message.models import (
+    AlephMessage,
+    Chain,
+    ExecutableContent,
+    ItemHash,
+    ItemType,
+    MessageType,
+    ProgramContent,
+)
 from aleph_message.status import MessageStatus
 from pydantic import ValidationError
 
@@ -37,6 +46,7 @@
 from ..utils import (
     Writable,
     check_unix_socket_valid,
+    compute_sha256,
     copy_async_readable_to_buffer,
     extended_json_encoder,
     get_message_type_value,
@@ -358,7 +368,7 @@ async def get_messages(
             )
 
     @overload
-    async def get_message(
+    async def get_message(  # type: ignore
         self,
         item_hash: str,
         message_type: Optional[Type[GenericMessage]] = None,
@@ -383,7 +393,7 @@ async def get_message(
                 resp.raise_for_status()
             except aiohttp.ClientResponseError as e:
                 if e.status == 404:
-                    raise MessageNotFoundError(f"No such hash {item_hash}")
+                    raise MessageNotFoundError(f"No such hash {item_hash}") from e
                 raise e
             message_raw = await resp.json()
         if message_raw["status"] == "forgotten":
@@ -399,9 +409,9 @@ async def get_message(
                     f"does not match the expected type '{expected_type}'"
                 )
         if with_status:
-            return message, message_raw["status"]
+            return message, message_raw["status"]  # type: ignore
         else:
-            return message
+            return message  # type: ignore
 
     async def get_message_error(
         self,
@@ -448,6 +458,47 @@ async def watch_messages(
                 elif msg.type == aiohttp.WSMsgType.ERROR:
                     break
 
+    async def get_estimated_price(
+        self,
+        content: ExecutableContent,
+    ) -> PriceResponse:
+        cleaned_content = content.dict(exclude_none=True)
+        item_content: str = json.dumps(
+            cleaned_content,
+            separators=(",", ":"),
+            default=extended_json_encoder,
+        )
+        message = parse_message(
+            dict(
+                sender=content.address,
+                chain=Chain.ETH,
+                type=(
+                    MessageType.program
+                    if isinstance(content, ProgramContent)
+                    else MessageType.instance
+                ),
+                content=cleaned_content,
+                item_content=item_content,
+                time=time.time(),
+                channel=settings.DEFAULT_CHANNEL,
+                item_type=ItemType.inline,
+                item_hash=compute_sha256(item_content),
+            )
+        )
+
+        async with self.http_session.post(
+            "/api/v0/price/estimate", json=dict(message=message)
+        ) as resp:
+            try:
+                resp.raise_for_status()
+                response_json = await resp.json()
+                return PriceResponse(
+                    required_tokens=response_json["required_tokens"],
+                    payment_type=response_json["payment_type"],
+                )
+            except aiohttp.ClientResponseError as e:
+                raise e
+
     async def get_program_price(self, item_hash: str) -> PriceResponse:
         async with self.http_session.get(f"/api/v0/price/{item_hash}") as resp:
             try:
@@ -491,15 +542,21 @@ async def get_stored_content(
                 resp = f"Invalid CID: {message.content.item_hash}"
             else:
                 filename = safe_getattr(message.content, "metadata.name")
-                hash = message.content.item_hash
+                item_hash = message.content.item_hash
                 url = (
                     f"{self.api_server}/api/v0/storage/raw/"
-                    if len(hash) == 64
+                    if len(item_hash) == 64
                     else settings.IPFS_GATEWAY
-                ) + hash
-                result = StoredContent(filename=filename, hash=hash, url=url)
+                ) + item_hash
+                result = StoredContent(
+                    filename=filename, hash=item_hash, url=url, error=None
+                )
         except MessageNotFoundError:
             resp = f"Message not found: {item_hash}"
         except ForgottenMessageError:
             resp = f"Message forgotten: {item_hash}"
-        return result if result else StoredContent(error=resp)
+        return (
+            result
+            if result
+            else StoredContent(error=resp, filename=None, hash=None, url=None)
+        )
diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 16ddc4a1..c81c9367 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -45,27 +45,22 @@ class Settings(BaseSettings):
     HTTP_REQUEST_TIMEOUT: float = 15.0
 
     DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS"
+
+    # Firecracker runtime for programs
     DEFAULT_RUNTIME_ID: str = (
         "63f07193e6ee9d207b7d1fcf8286f9aee34e6f12f101d2ec77c1229f92964696"
     )
-    DEBIAN_11_ROOTFS_ID: str = (
-        "887957042bb0e360da3485ed33175882ce72a70d79f1ba599400ff4802b7cee7"
-    )
-    DEBIAN_12_ROOTFS_ID: str = (
-        "6e30de68c6cedfa6b45240c2b51e52495ac6fb1bd4b36457b3d5ca307594d595"
-    )
-    UBUNTU_22_ROOTFS_ID: str = (
-        "77fef271aa6ff9825efa3186ca2e715d19e7108279b817201c69c34cedc74c27"
-    )
-    DEBIAN_11_QEMU_ROOTFS_ID: str = (
-        "f7e68c568906b4ebcd3cd3c4bfdff96c489cd2a9ef73ba2d7503f244dfd578de"
-    )
+
+    # Qemu rootfs for instances
     DEBIAN_12_QEMU_ROOTFS_ID: str = (
         "b6ff5c3a8205d1ca4c7c3369300eeafff498b558f71b851aa2114afd0a532717"
     )
     UBUNTU_22_QEMU_ROOTFS_ID: str = (
         "4a0f62da42f4478544616519e6f5d58adb1096e069b392b151d47c3609492d0c"
     )
+    UBUNTU_24_QEMU_ROOTFS_ID: str = (
+        "5330dcefe1857bcd97b7b7f24d1420a7d46232d53f27be280c8a7071d88bd84e"
+    )
 
     DEFAULT_CONFIDENTIAL_FIRMWARE: str = (
         "ba5bb13f3abca960b101a759be162b229e2b7e93ecad9d1307e54de887f177ff"
@@ -87,6 +82,7 @@ class Settings(BaseSettings):
     VM_URL_PATH: ClassVar[str] = "https://aleph.sh/vm/{hash}"
     VM_URL_HOST: ClassVar[str] = "https://{hash_base32}.aleph.sh"
     IPFS_GATEWAY: ClassVar[str] = "https://ipfs.aleph.cloud/ipfs/"
+    CRN_URL_FOR_PROGRAMS: ClassVar[str] = "https://dchq.staging.aleph.sh/"
 
     # Web3Provider settings
     TOKEN_DECIMALS: ClassVar[int] = 18
diff --git a/src/aleph/sdk/connectors/superfluid.py b/src/aleph/sdk/connectors/superfluid.py
index 4b7274f8..76bbf907 100644
--- a/src/aleph/sdk/connectors/superfluid.py
+++ b/src/aleph/sdk/connectors/superfluid.py
@@ -1,14 +1,19 @@
 from __future__ import annotations
 
 from decimal import Decimal
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Optional
 
 from eth_utils import to_normalized_address
 from superfluid import CFA_V1, Operation, Web3FlowInfo
 
+from aleph.sdk.evm_utils import (
+    FlowUpdate,
+    from_wei_token,
+    get_super_token_address,
+    to_wei_token,
+)
 from aleph.sdk.exceptions import InsufficientFundsError
-
-from ..evm_utils import get_super_token_address, to_human_readable_token, to_wei_token
+from aleph.sdk.types import TokenType
 
 if TYPE_CHECKING:
     from aleph.sdk.chains.ethereum import ETHAccount
@@ -44,6 +49,7 @@ async def _execute_operation_with_account(self, operation: Operation) -> str:
         return await self.account._sign_and_send_transaction(populated_transaction)
 
     def can_start_flow(self, flow: Decimal, block=True) -> bool:
+        """Check if the account has enough funds to start a Superfluid flow of the given size."""
         valid = False
         if self.account.can_transact(block=block):
             balance = self.account.get_super_token_balance()
@@ -51,8 +57,9 @@ def can_start_flow(self, flow: Decimal, block=True) -> bool:
             valid = balance > MIN_FLOW_4H
             if not valid and block:
                 raise InsufficientFundsError(
-                    required_funds=float(MIN_FLOW_4H),
-                    available_funds=to_human_readable_token(balance),
+                    token_type=TokenType.ALEPH,
+                    required_funds=float(from_wei_token(MIN_FLOW_4H)),
+                    available_funds=float(from_wei_token(balance)),
                 )
         return valid
 
@@ -96,3 +103,51 @@ async def update_flow(self, receiver: str, flow: Decimal) -> str:
                 flow_rate=int(to_wei_token(flow)),
             ),
         )
+
+    async def manage_flow(
+        self,
+        receiver: str,
+        flow: Decimal,
+        update_type: FlowUpdate,
+    ) -> Optional[str]:
+        """
+        Update the flow of a Superfluid stream between a sender and receiver.
+        This function either increases or decreases the flow rate between the sender and receiver,
+        based on the update_type. If no flow exists and the update type is augmentation, it creates a new flow
+        with the specified rate. If the update type is reduction and the reduction amount brings the flow to zero
+        or below, the flow is deleted.
+
+        :param receiver: Address of the receiver in hexadecimal format.
+        :param flow: The flow rate to be added or removed (in ether).
+        :param update_type: The type of update to perform (augmentation or reduction).
+        :return: The transaction hash of the executed operation (create, update, or delete flow).
+        """
+
+        # Retrieve current flow info
+        flow_info: Web3FlowInfo = await self.account.get_flow(receiver)
+
+        current_flow_rate_wei: Decimal = Decimal(flow_info["flowRate"] or 0)
+        flow_rate_wei: int = int(to_wei_token(flow))
+
+        if update_type == FlowUpdate.INCREASE:
+            if current_flow_rate_wei > 0:
+                # Update existing flow by increasing the rate
+                new_flow_rate_wei = current_flow_rate_wei + flow_rate_wei
+                new_flow_rate_ether = from_wei_token(new_flow_rate_wei)
+                return await self.account.update_flow(receiver, new_flow_rate_ether)
+            else:
+                # Create a new flow if none exists
+                return await self.account.create_flow(receiver, flow)
+        else:
+            if current_flow_rate_wei > 0:
+                # Reduce the existing flow
+                new_flow_rate_wei = current_flow_rate_wei - flow_rate_wei
+                # Ensure to not leave infinitesimal flows
+                # Often, there were 1-10 wei remaining in the flow rate, which prevented the flow from being deleted
+                if new_flow_rate_wei > 99:
+                    new_flow_rate_ether = from_wei_token(new_flow_rate_wei)
+                    return await self.account.update_flow(receiver, new_flow_rate_ether)
+                else:
+                    # Delete the flow if the new flow rate is zero or negative
+                    return await self.account.delete_flow(receiver)
+        return None
diff --git a/src/aleph/sdk/evm_utils.py b/src/aleph/sdk/evm_utils.py
index 4d2026ef..a425d580 100644
--- a/src/aleph/sdk/evm_utils.py
+++ b/src/aleph/sdk/evm_utils.py
@@ -1,4 +1,5 @@
-from decimal import Decimal
+from decimal import ROUND_CEILING, Context, Decimal
+from enum import Enum
 from typing import List, Optional, Union
 
 from aleph_message.models import Chain
@@ -21,12 +22,26 @@
 }]"""
 
 
-def to_human_readable_token(amount: Decimal) -> float:
-    return float(amount / (Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)))
+class FlowUpdate(str, Enum):
+    REDUCE = "reduce"
+    INCREASE = "increase"
+
+
+def ether_rounding(amount: Decimal) -> Decimal:
+    """Rounds the given value to 18 decimals."""
+    return amount.quantize(
+        Decimal(1) / Decimal(10**18), rounding=ROUND_CEILING, context=Context(prec=36)
+    )
+
+
+def from_wei_token(amount: Decimal) -> Decimal:
+    """Converts the given wei value to ether."""
+    return ether_rounding(amount / Decimal(10) ** Decimal(settings.TOKEN_DECIMALS))
 
 
 def to_wei_token(amount: Decimal) -> Decimal:
-    return amount * Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)
+    """Converts the given ether value to wei."""
+    return Decimal(int(amount * Decimal(10) ** Decimal(settings.TOKEN_DECIMALS)))
 
 
 def get_chain_id(chain: Union[Chain, str, None]) -> Optional[int]:
diff --git a/src/aleph/sdk/exceptions.py b/src/aleph/sdk/exceptions.py
index a538a31c..05ed755f 100644
--- a/src/aleph/sdk/exceptions.py
+++ b/src/aleph/sdk/exceptions.py
@@ -1,5 +1,8 @@
 from abc import ABC
 
+from .types import TokenType
+from .utils import displayable_amount
+
 
 class QueryError(ABC, ValueError):
     """The result of an API query is inconsistent."""
@@ -69,14 +72,18 @@ class ForgottenMessageError(QueryError):
 class InsufficientFundsError(Exception):
     """Raised when the account does not have enough funds to perform an action"""
 
+    token_type: TokenType
     required_funds: float
     available_funds: float
 
-    def __init__(self, required_funds: float, available_funds: float):
+    def __init__(
+        self, token_type: TokenType, required_funds: float, available_funds: float
+    ):
+        self.token_type = token_type
         self.required_funds = required_funds
         self.available_funds = available_funds
         super().__init__(
-            f"Insufficient funds: required {required_funds}, available {available_funds}"
+            f"Insufficient funds ({self.token_type.value}): required {displayable_amount(self.required_funds, decimals=8)}, available {displayable_amount(self.available_funds, decimals=8)}"
         )
 
 
diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py
index e192335f..cf23f19d 100644
--- a/src/aleph/sdk/types.py
+++ b/src/aleph/sdk/types.py
@@ -83,7 +83,20 @@ class ChainInfo(BaseModel):
 
 
 class StoredContent(BaseModel):
+    """
+    A stored content.
+    """
+
     filename: Optional[str] = Field(default=None)
     hash: Optional[str] = Field(default=None)
     url: Optional[str] = Field(default=None)
     error: Optional[str] = Field(default=None)
+
+
+class TokenType(str, Enum):
+    """
+    A token type.
+    """
+
+    GAS = "GAS"
+    ALEPH = "ALEPH"
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index a6ec40a8..e933440a 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -8,6 +8,7 @@
 import os
 import subprocess
 from datetime import date, datetime, time
+from decimal import Context, Decimal, InvalidOperation
 from enum import Enum
 from pathlib import Path
 from shutil import make_archive
@@ -15,7 +16,6 @@
     Any,
     Dict,
     Iterable,
-    List,
     Mapping,
     Optional,
     Protocol,
@@ -28,10 +28,38 @@
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
 
-import pydantic_core
-from aleph_message.models import ItemHash, MessageType
-from aleph_message.models.execution.program import Encoding
-from aleph_message.models.execution.volume import MachineVolume
+from aleph_message.models import (
+    Chain,
+    InstanceContent,
+    ItemHash,
+    MachineType,
+    MessageType,
+    ProgramContent,
+)
+from aleph_message.models.execution.base import Payment, PaymentType
+from aleph_message.models.execution.environment import (
+    FunctionEnvironment,
+    FunctionTriggers,
+    HostRequirements,
+    HypervisorType,
+    InstanceEnvironment,
+    MachineResources,
+    Subscription,
+    TrustedExecutionEnvironment,
+)
+from aleph_message.models.execution.instance import RootfsVolume
+from aleph_message.models.execution.program import (
+    CodeContent,
+    Encoding,
+    FunctionRuntime,
+)
+from aleph_message.models.execution.volume import (
+    MachineVolume,
+    ParentVolume,
+    PersistentVolumeSizeMib,
+    VolumePersistence,
+)
+from aleph_message.utils import Mebibytes
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
@@ -179,13 +207,17 @@ def extended_json_encoder(obj: Any) -> Any:
 def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
     # Python 3.9 does not support `isinstance(volume_dict, MachineVolume)`,
     # so we need to iterate over all types.
+    if any(
+        isinstance(volume_dict, volume_type) for volume_type in get_args(MachineVolume)
+    ):
+        return volume_dict  # type: ignore
+
     for volume_type in get_args(MachineVolume):
         try:
             return volume_type.model_validate(volume_dict)
         except ValueError:
-            continue
-    else:
-        raise ValueError(f"Could not parse volume: {volume_dict}")
+            pass
+    raise ValueError(f"Could not parse volume: {volume_dict}")
 
 
 def compute_sha256(s: str) -> str:
@@ -230,7 +262,7 @@ def sign_vm_control_payload(payload: Dict[str, str], ephemeral_key) -> str:
 
 
 async def run_in_subprocess(
-    command: List[str], check: bool = True, stdin_input: Optional[bytes] = None
+    command: list[str], check: bool = True, stdin_input: Optional[bytes] = None
 ) -> bytes:
     """Run the specified command in a subprocess, returns the stdout of the process."""
     logger.debug(f"command: {' '.join(command)}")
@@ -397,3 +429,166 @@ def safe_getattr(obj, attr, default=None):
         if obj is default:
             break
     return obj
+
+
+def displayable_amount(
+    amount: Union[str, int, float, Decimal], decimals: int = 18
+) -> str:
+    """Returns the amount as a string without unnecessary decimals."""
+
+    str_amount = ""
+    try:
+        dec_amount = Decimal(amount)
+        if decimals:
+            dec_amount = dec_amount.quantize(
+                Decimal(1) / Decimal(10**decimals), context=Context(prec=36)
+            )
+        str_amount = str(format(dec_amount.normalize(), "f"))
+    except ValueError:
+        logger.error(f"Invalid amount to display: {amount}")
+        exit(1)
+    except InvalidOperation:
+        logger.error(f"Invalid operation on amount to display: {amount}")
+        exit(1)
+    return str_amount
+
+
+def make_instance_content(
+    rootfs: str,
+    rootfs_size: int,
+    payment: Optional[Payment] = None,
+    environment_variables: Optional[dict[str, str]] = None,
+    address: Optional[str] = None,
+    memory: Optional[int] = None,
+    vcpus: Optional[int] = None,
+    timeout_seconds: Optional[float] = None,
+    allow_amend: bool = False,
+    internet: bool = True,
+    aleph_api: bool = True,
+    hypervisor: Optional[HypervisorType] = None,
+    trusted_execution: Optional[TrustedExecutionEnvironment] = None,
+    volumes: Optional[list[Mapping]] = None,
+    ssh_keys: Optional[list[str]] = None,
+    metadata: Optional[dict[str, Any]] = None,
+    requirements: Optional[HostRequirements] = None,
+) -> InstanceContent:
+    """
+    Create InstanceContent object given the provided fields.
+    """
+
+    address = address or "0x0000000000000000000000000000000000000000"
+    payment = payment or Payment(chain=Chain.ETH, type=PaymentType.hold, receiver=None)
+    selected_hypervisor: HypervisorType = hypervisor or HypervisorType.qemu
+    vcpus = vcpus or settings.DEFAULT_VM_VCPUS
+    memory = memory or settings.DEFAULT_VM_MEMORY
+    timeout_seconds = timeout_seconds or settings.DEFAULT_VM_TIMEOUT
+    volumes = volumes if volumes is not None else []
+
+    return InstanceContent(
+        address=address,
+        allow_amend=allow_amend,
+        environment=InstanceEnvironment(
+            internet=internet,
+            aleph_api=aleph_api,
+            hypervisor=selected_hypervisor,
+            trusted_execution=trusted_execution,
+        ),
+        variables=environment_variables,
+        resources=MachineResources(
+            vcpus=vcpus,
+            memory=Mebibytes(memory),
+            seconds=int(timeout_seconds),
+        ),
+        rootfs=RootfsVolume(
+            parent=ParentVolume(
+                ref=ItemHash(rootfs),
+                use_latest=True,
+            ),
+            size_mib=PersistentVolumeSizeMib(rootfs_size),
+            persistence=VolumePersistence.host,
+        ),
+        volumes=[parse_volume(volume) for volume in volumes],
+        requirements=requirements,
+        time=datetime.now().timestamp(),
+        authorized_keys=ssh_keys,
+        metadata=metadata,
+        payment=payment,
+    )
+
+
+def make_program_content(
+    program_ref: str,
+    entrypoint: str,
+    runtime: str,
+    metadata: Optional[dict[str, Any]] = None,
+    address: Optional[str] = None,
+    vcpus: Optional[int] = None,
+    memory: Optional[int] = None,
+    timeout_seconds: Optional[float] = None,
+    internet: bool = False,
+    aleph_api: bool = True,
+    allow_amend: bool = False,
+    encoding: Encoding = Encoding.zip,
+    persistent: bool = False,
+    volumes: Optional[list[Mapping]] = None,
+    environment_variables: Optional[dict[str, str]] = None,
+    subscriptions: Optional[list[dict]] = None,
+    payment: Optional[Payment] = None,
+) -> ProgramContent:
+    """
+    Create ProgramContent object given the provided fields.
+    """
+
+    address = address or "0x0000000000000000000000000000000000000000"
+    payment = payment or Payment(chain=Chain.ETH, type=PaymentType.hold, receiver=None)
+    vcpus = vcpus or settings.DEFAULT_VM_VCPUS
+    memory = memory or settings.DEFAULT_VM_MEMORY
+    timeout_seconds = timeout_seconds or settings.DEFAULT_VM_TIMEOUT
+    volumes = volumes if volumes is not None else []
+    subscriptions = (
+        [Subscription(**sub) for sub in subscriptions]
+        if subscriptions is not None
+        else None
+    )
+
+    return ProgramContent(
+        type=MachineType.vm_function,
+        address=address,
+        allow_amend=allow_amend,
+        code=CodeContent(
+            encoding=encoding,
+            entrypoint=entrypoint,
+            ref=ItemHash(program_ref),
+            use_latest=True,
+        ),
+        on=FunctionTriggers(
+            http=True,
+            persistent=persistent,
+            message=subscriptions,
+        ),
+        environment=FunctionEnvironment(
+            reproducible=False,
+            internet=internet,
+            aleph_api=aleph_api,
+        ),
+        variables=environment_variables,
+        resources=MachineResources(
+            vcpus=vcpus,
+            memory=Mebibytes(memory),
+            seconds=int(timeout_seconds),
+        ),
+        runtime=FunctionRuntime(
+            ref=ItemHash(runtime),
+            use_latest=True,
+            comment=(
+                "Official aleph.im runtime"
+                if runtime == settings.DEFAULT_RUNTIME_ID
+                else ""
+            ),
+        ),
+        volumes=[parse_volume(volume) for volume in volumes],
+        time=datetime.now().timestamp(),
+        metadata=metadata,
+        authorized_keys=[],
+        payment=payment,
+    )
diff --git a/tests/unit/test_asynchronous.py b/tests/unit/test_asynchronous.py
index b044e170..e2647590 100644
--- a/tests/unit/test_asynchronous.py
+++ b/tests/unit/test_asynchronous.py
@@ -7,6 +7,7 @@
     Chain,
     ForgetMessage,
     InstanceMessage,
+    ItemHash,
     MessageType,
     Payment,
     PaymentType,
@@ -184,12 +185,16 @@ async def test_create_confidential_instance(mock_session_with_post_success):
             ),
             hypervisor=HypervisorType.qemu,
             trusted_execution=TrustedExecutionEnvironment(
-                firmware="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe",
+                firmware=ItemHash(
+                    "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe"
+                ),
                 policy=0b1,
             ),
             requirements=HostRequirements(
                 node=NodeRequirements(
-                    node_hash="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe",
+                    node_hash=ItemHash(
+                        "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe"
+                    ),
                 )
             ),
         )
@@ -285,5 +290,6 @@ async def test_create_instance_insufficient_funds_error(
                 payment=Payment(
                     chain=Chain.ETH,
                     type=PaymentType.hold,
+                    receiver=None,
                 ),
             )
diff --git a/tests/unit/test_price.py b/tests/unit/test_price.py
index b596c33b..830fb931 100644
--- a/tests/unit/test_price.py
+++ b/tests/unit/test_price.py
@@ -11,11 +11,11 @@ async def test_get_program_price_valid():
     Test that the get_program_price method returns the correct PriceResponse
     when given a valid item hash.
     """
-    expected_response = {
-        "required_tokens": 3.0555555555555556e-06,
-        "payment_type": "superfluid",
-    }
-    mock_session = make_mock_get_session(expected_response)
+    expected = PriceResponse(
+        required_tokens=3.0555555555555556e-06,
+        payment_type="superfluid",
+    )
+    mock_session = make_mock_get_session(expected.dict())
     async with mock_session:
         response = await mock_session.get_program_price("cacacacacacaca")
         assert response == PriceResponse(**expected_response)  # type: ignore
diff --git a/tests/unit/test_superfluid.py b/tests/unit/test_superfluid.py
index c2f853bd..74bcc38e 100644
--- a/tests/unit/test_superfluid.py
+++ b/tests/unit/test_superfluid.py
@@ -7,6 +7,7 @@
 from eth_utils import to_checksum_address
 
 from aleph.sdk.chains.ethereum import ETHAccount
+from aleph.sdk.evm_utils import FlowUpdate
 
 
 def generate_fake_eth_address():
@@ -24,6 +25,7 @@ def mock_superfluid():
         mock_superfluid.create_flow = AsyncMock(return_value="0xTransactionHash")
         mock_superfluid.delete_flow = AsyncMock(return_value="0xTransactionHash")
         mock_superfluid.update_flow = AsyncMock(return_value="0xTransactionHash")
+        mock_superfluid.manage_flow = AsyncMock(return_value="0xTransactionHash")
 
         # Mock get_flow to return a mock Web3FlowInfo
         mock_flow_info = {"timestamp": 0, "flowRate": 0, "deposit": 0, "owedDeposit": 0}
@@ -98,3 +100,14 @@ async def test_get_flow(eth_account, mock_superfluid):
     assert flow_info["flowRate"] == 0
     assert flow_info["deposit"] == 0
     assert flow_info["owedDeposit"] == 0
+
+
+@pytest.mark.asyncio
+async def test_manage_flow(eth_account, mock_superfluid):
+    receiver = generate_fake_eth_address()
+    flow = Decimal("0.005")
+
+    tx_hash = await eth_account.manage_flow(receiver, flow, FlowUpdate.INCREASE)
+
+    assert tx_hash == "0xTransactionHash"
+    mock_superfluid.manage_flow.assert_awaited_once()
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 8e4083c0..4ceb5a3f 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -1,5 +1,6 @@
 import base64
 import datetime
+from unittest.mock import MagicMock
 
 import pytest as pytest
 from aleph_message.models import (
@@ -158,6 +159,7 @@ def test_parse_immutable_volume():
 def test_parse_ephemeral_volume():
     volume_dict = {
         "comment": "Dummy hash",
+        "mount": "/opt/data",
         "ephemeral": True,
         "size_mib": 1,
     }
@@ -169,6 +171,8 @@ def test_parse_ephemeral_volume():
 
 def test_parse_persistent_volume():
     volume_dict = {
+        "comment": "Dummy hash",
+        "mount": "/opt/data",
         "parent": {
             "ref": "QmX8K1c22WmQBAww5ShWQqwMiFif7XFrJD6iFBj7skQZXW",
             "use_latest": True,
@@ -184,9 +188,9 @@ def test_parse_persistent_volume():
     assert isinstance(volume, PersistentVolume)
 
 
-def test_calculate_firmware_hash(mocker):
-    mock_path = mocker.Mock(
-        read_bytes=mocker.Mock(return_value=b"abc"),
+def test_calculate_firmware_hash():
+    mock_path = MagicMock(
+        read_bytes=MagicMock(return_value=b"abc"),
     )
 
     assert (

From 5fdc4a8fe33d1ef35007a3bdad3fd5a6b4c5b5c1 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 4 Oct 2024 10:59:02 +0900
Subject: [PATCH 22/43] Migrate to Pydantic v2, update model validation and fix
 async issues

- Migrated to Pydantic v2:
  - Replaced deprecated `parse_obj()` and `parse_raw()` with
`model_validate()` and `model_validate_json()`.
  - Replaced `.dict()` with `.model_dump()` for serializing models to dictionaries.
  - Updated `validator` to `field_validator` and `root_validator` to
`model_validator` to comply with Pydantic v2 syntax changes.

- Fixed asyncio issues:
  - Added `await` for asynchronous methods like `raise_for_status()`
in `RemoteAccount` and other HTTP operations to avoid `RuntimeWarning`.

- Updated config handling:
  - Used `ClassVar` for constants in `Settings` and other configuration classes.
  - Replaced `Config` with `ConfigDict` in Pydantic models
to follow v2 conventions.
  - Added default values for missing fields in chain configurations
(`CHAINS_SEPOLIA_ACTIVE`, etc.).

- Adjusted signature handling:
  - Updated the signing logic to prepend `0x` in the `BaseAccount` signature
generation to ensure correct Ethereum address formatting.

- Minor fixes:
  - Resolved issue with extra fields not being allowed by default
by specifying `extra="allow"` or `extra="forbid"` where necessary.
  - Fixed tests to account for changes in model validation and
serialization behavior.
  - Added `pydantic-settings` as a new dependency for configuration management.
---
 pyproject.toml                        |  6 +++---
 src/aleph/sdk/conf.py                 | 15 +++++++++------
 src/aleph/sdk/utils.py                |  1 +
 tests/unit/aleph_vm_authentication.py |  9 +++++----
 tests/unit/test_utils.py              | 18 +++++++++---------
 5 files changed, 27 insertions(+), 22 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index bc28fb15..f3384155 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,15 +34,15 @@ dependencies = [
   "aleph-superfluid>=0.2.1",
   "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
-  "coincurve>=19; python_version>='3.11'",
-  "eth-abi>=4; python_version>='3.11'",
+  "coincurve>=19.0.0; python_version>='3.11'",
+  "eth-abi>=4.0.O; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",
   "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
-  "web3==6.3",
+  "web3==6.3.0",
 ]
 
 optional-dependencies.all = [
diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index c81c9367..68098092 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,10 +5,11 @@
 from shutil import which
 from typing import ClassVar, Dict, List, Optional, Union
 
+from pydantic_settings import BaseSettings
+
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
-from pydantic import BaseModel, Field
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import BaseModel, ConfigDict, Field
 
 from aleph.sdk.types import ChainInfo
 
@@ -42,7 +43,7 @@ class Settings(BaseSettings):
     REMOTE_CRYPTO_HOST: Optional[str] = None
     REMOTE_CRYPTO_UNIX_SOCKET: Optional[str] = None
     ADDRESS_TO_USE: Optional[str] = None
-    HTTP_REQUEST_TIMEOUT: float = 15.0
+    HTTP_REQUEST_TIMEOUT: ClassVar[float] = 15.0
 
     DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS"
 
@@ -221,8 +222,10 @@ class Settings(BaseSettings):
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
     DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
-    model_config = SettingsConfigDict(
-        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
+    model_config = ConfigDict(
+       env_prefix="ALEPH_",
+       case_sensitive=False,
+       env_file=".env"
     )
 
 
@@ -234,7 +237,7 @@ class MainConfiguration(BaseModel):
     path: Path
     chain: Chain
 
-    model_config = SettingsConfigDict(use_enum_values=True)
+    model_config = ConfigDict(use_enum_values = True)
 
 
 # Settings singleton
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index e933440a..80b2be58 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -63,6 +63,7 @@
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
+import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index c1710c16..324868be 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -75,8 +75,8 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the payload from hexadecimal to bytes"""
         return bytes_from_hex(value.decode())
 
-    @model_validator(mode="after")  # type: ignore
-    def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
+    @model_validator(mode="after")
+    def check_expiry(cls, values) -> Dict[str, bytes]:
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -87,8 +87,9 @@ def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
 
         return values
 
-    @model_validator(mode="after")  # type: ignore
-    def check_signature(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
+    @model_validator(mode="after")
+    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
+        """Check that the signature is valid"""
         signature: bytes = values.signature
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 4ceb5a3f..0d843394 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -115,17 +115,17 @@ def test_enum_as_str():
         (
             MessageType.aggregate,
             {
-                "address": "0x1",
-                "content": {
-                    "Hello": {
-                        "vcpus": 1,
-                        "memory": 1024,
-                        "seconds": 1,
-                        "published_ports": None,
+                'address': '0x1',
+                'content': {
+                    'Hello': {
+                        'vcpus': 1,
+                        'memory': 1024,
+                        'seconds': 1,
+                        'published_ports': None,
                     },
                 },
-                "key": "test",
-                "time": 1.0,
+                'key': 'test',
+                'time': 1.0,
             },
         ),
     ],

From 696cf90924da3c7247d96915c408371ca7b3f597 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 4 Oct 2024 11:46:22 +0900
Subject: [PATCH 23/43] fix: lint tests were failing

- Updated all instances of **extra_fields to ensure proper handling of
Optional dictionaries using `(extra_fields or {})` pattern.
- Added proper return statements in `AlephHttpClient.get_message_status`
to return parsed JSON data as a `MessageStatus` object.
- Updated `Settings` class in `conf.py` to correct DNS resolvers type
and simplify the `model_config` definition.
- Refactored `parse_volume` to ensure correct handling of Mapping types
and MachineVolume types, avoiding TypeErrors.
- Improved field validation and model validation in `SignedPubKeyHeader` by
using correct Pydantic v2 validation decorators and ensuring compatibility with the new model behavior.
- Applied formatting and consistency fixes for `model_dump` usage and indentation improvements in test files.
---
 src/aleph/sdk/conf.py                 |  9 +++------
 src/aleph/sdk/utils.py                |  1 -
 tests/unit/aleph_vm_authentication.py |  4 ++--
 tests/unit/test_utils.py              | 18 +++++++++---------
 4 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index 68098092..b29bf4f7 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,11 +5,10 @@
 from shutil import which
 from typing import ClassVar, Dict, List, Optional, Union
 
-from pydantic_settings import BaseSettings
-
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
 from pydantic import BaseModel, ConfigDict, Field
+from pydantic_settings import BaseSettings
 
 from aleph.sdk.types import ChainInfo
 
@@ -223,9 +222,7 @@ class Settings(BaseSettings):
     DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
-       env_prefix="ALEPH_",
-       case_sensitive=False,
-       env_file=".env"
+        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
     )
 
 
@@ -237,7 +234,7 @@ class MainConfiguration(BaseModel):
     path: Path
     chain: Chain
 
-    model_config = ConfigDict(use_enum_values = True)
+    model_config = ConfigDict(use_enum_values=True)
 
 
 # Settings singleton
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index 80b2be58..e933440a 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -63,7 +63,6 @@
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
-import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index 324868be..2fd1cf45 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -76,7 +76,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values) -> Dict[str, bytes]:
+    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -88,7 +88,7 @@ def check_expiry(cls, values) -> Dict[str, bytes]:
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
+    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         """Check that the signature is valid"""
         signature: bytes = values.signature
         payload: bytes = values.payload
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 0d843394..4ceb5a3f 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -115,17 +115,17 @@ def test_enum_as_str():
         (
             MessageType.aggregate,
             {
-                'address': '0x1',
-                'content': {
-                    'Hello': {
-                        'vcpus': 1,
-                        'memory': 1024,
-                        'seconds': 1,
-                        'published_ports': None,
+                "address": "0x1",
+                "content": {
+                    "Hello": {
+                        "vcpus": 1,
+                        "memory": 1024,
+                        "seconds": 1,
+                        "published_ports": None,
                     },
                 },
-                'key': 'test',
-                'time': 1.0,
+                "key": "test",
+                "time": 1.0,
             },
         ),
     ],

From 5b9148a7c24130a8876f9f7a2aee4faddc71859e Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 4 Oct 2024 10:59:02 +0900
Subject: [PATCH 24/43] Migrate to Pydantic v2, update model validation and fix
 async issues

- Migrated to Pydantic v2:
  - Replaced deprecated `parse_obj()` and `parse_raw()` with
`model_validate()` and `model_validate_json()`.
  - Replaced `.dict()` with `.model_dump()` for serializing models to dictionaries.
  - Updated `validator` to `field_validator` and `root_validator` to
`model_validator` to comply with Pydantic v2 syntax changes.

- Fixed asyncio issues:
  - Added `await` for asynchronous methods like `raise_for_status()`
in `RemoteAccount` and other HTTP operations to avoid `RuntimeWarning`.

- Updated config handling:
  - Used `ClassVar` for constants in `Settings` and other configuration classes.
  - Replaced `Config` with `ConfigDict` in Pydantic models
to follow v2 conventions.
  - Added default values for missing fields in chain configurations
(`CHAINS_SEPOLIA_ACTIVE`, etc.).

- Adjusted signature handling:
  - Updated the signing logic to prepend `0x` in the `BaseAccount` signature
generation to ensure correct Ethereum address formatting.

- Minor fixes:
  - Resolved issue with extra fields not being allowed by default
by specifying `extra="allow"` or `extra="forbid"` where necessary.
  - Fixed tests to account for changes in model validation and
serialization behavior.
  - Added `pydantic-settings` as a new dependency for configuration management.
---
 src/aleph/sdk/conf.py                 | 7 +++++--
 src/aleph/sdk/utils.py                | 1 +
 tests/unit/aleph_vm_authentication.py | 6 +++---
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index b29bf4f7..c0df405e 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,10 +5,11 @@
 from shutil import which
 from typing import ClassVar, Dict, List, Optional, Union
 
+from pydantic_settings import BaseSettings
+
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
 from pydantic import BaseModel, ConfigDict, Field
-from pydantic_settings import BaseSettings
 
 from aleph.sdk.types import ChainInfo
 
@@ -222,7 +223,9 @@ class Settings(BaseSettings):
     DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
-        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
+       env_prefix="ALEPH_",
+       case_sensitive=False,
+       env_file=".env"
     )
 
 
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index e933440a..80b2be58 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -63,6 +63,7 @@
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
+import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index 2fd1cf45..fd65be0c 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -15,7 +15,7 @@
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 from jwcrypto.jwa import JWA
-from pydantic import BaseModel, ValidationError, field_validator, model_validator
+from pydantic import BaseModel, ValidationError, model_validator, field_validator
 
 from aleph.sdk.utils import bytes_from_hex
 
@@ -76,7 +76,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_expiry(cls, values) -> Dict[str, bytes]:
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -88,7 +88,7 @@ def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
         """Check that the signature is valid"""
         signature: bytes = values.signature
         payload: bytes = values.payload

From be79a36568ee8f660e93801bc50707d6cf5c4763 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 15 Oct 2024 12:02:00 +0900
Subject: [PATCH 25/43] Fix: Linting tests did not pass:

---
 src/aleph/sdk/conf.py                 | 7 ++-----
 src/aleph/sdk/utils.py                | 1 -
 tests/unit/aleph_vm_authentication.py | 7 +++----
 3 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index c0df405e..b29bf4f7 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -5,11 +5,10 @@
 from shutil import which
 from typing import ClassVar, Dict, List, Optional, Union
 
-from pydantic_settings import BaseSettings
-
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
 from pydantic import BaseModel, ConfigDict, Field
+from pydantic_settings import BaseSettings
 
 from aleph.sdk.types import ChainInfo
 
@@ -223,9 +222,7 @@ class Settings(BaseSettings):
     DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
     model_config = ConfigDict(
-       env_prefix="ALEPH_",
-       case_sensitive=False,
-       env_file=".env"
+        env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
     )
 
 
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index 80b2be58..e933440a 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -63,7 +63,6 @@
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from jwcrypto.jwa import JWA
-import pydantic_core
 
 from aleph.sdk.conf import settings
 from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index fd65be0c..ea2f3c17 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -15,7 +15,7 @@
 from eth_account.messages import encode_defunct
 from jwcrypto import jwk
 from jwcrypto.jwa import JWA
-from pydantic import BaseModel, ValidationError, model_validator, field_validator
+from pydantic import BaseModel, ValidationError, field_validator, model_validator
 
 from aleph.sdk.utils import bytes_from_hex
 
@@ -76,7 +76,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values) -> Dict[str, bytes]:
+    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -88,8 +88,7 @@ def check_expiry(cls, values) -> Dict[str, bytes]:
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]:
-        """Check that the signature is valid"""
+    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         signature: bytes = values.signature
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)

From 322de8a7e582bfb0adf4fdfa4f8dcfa73c0a2636 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 5 Nov 2024 23:00:54 +0900
Subject: [PATCH 26/43] fix: Wrong aleph-message version

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index f3384155..6b084542 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,7 +35,7 @@ dependencies = [
   "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19.0.0; python_version>='3.11'",
-  "eth-abi>=4.0.O; python_version>='3.11'",
+  "eth-abi>=4.0.0; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
   "pydantic-settings>=2",

From bbbdb3f172b7b9dc4b9ee135af8172297d9afc99 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 29 Nov 2024 22:08:27 +0900
Subject: [PATCH 27/43] fix: Hugo comments

---
 tests/unit/aleph_vm_authentication.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index ea2f3c17..aa5c49c5 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -76,7 +76,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         return bytes_from_hex(value.decode())
 
     @model_validator(mode="after")
-    def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         """Check that the token has not expired"""
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)
@@ -88,7 +88,7 @@ def check_expiry(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
         return values
 
     @model_validator(mode="after")
-    def check_signature(cls, values: "SignedPubKeyHeader") -> "SignedPubKeyHeader":
+    def check_signature(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         signature: bytes = values.signature
         payload: bytes = values.payload
         content = SignedPubKeyPayload.model_validate_json(payload)

From f09f346467f84e4910006dab78bf9d5a0a602e10 Mon Sep 17 00:00:00 2001
From: philogicae <38438271+philogicae@users.noreply.github.com>
Date: Wed, 4 Dec 2024 17:09:46 +0200
Subject: [PATCH 28/43] Add pydantic for better mypy tests + Fixes

---
 src/aleph/sdk/conf.py                 | 8 ++++----
 tests/unit/aleph_vm_authentication.py | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py
index b29bf4f7..2d3a03e1 100644
--- a/src/aleph/sdk/conf.py
+++ b/src/aleph/sdk/conf.py
@@ -7,8 +7,8 @@
 
 from aleph_message.models import Chain
 from aleph_message.models.execution.environment import HypervisorType
-from pydantic import BaseModel, ConfigDict, Field
-from pydantic_settings import BaseSettings
+from pydantic import BaseModel, Field
+from pydantic_settings import BaseSettings, SettingsConfigDict
 
 from aleph.sdk.types import ChainInfo
 
@@ -221,7 +221,7 @@ class Settings(BaseSettings):
     DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh"
     DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"]
 
-    model_config = ConfigDict(
+    model_config = SettingsConfigDict(
         env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
     )
 
@@ -234,7 +234,7 @@ class MainConfiguration(BaseModel):
     path: Path
     chain: Chain
 
-    model_config = ConfigDict(use_enum_values=True)
+    model_config = SettingsConfigDict(use_enum_values=True)
 
 
 # Settings singleton
diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py
index aa5c49c5..c1710c16 100644
--- a/tests/unit/aleph_vm_authentication.py
+++ b/tests/unit/aleph_vm_authentication.py
@@ -75,7 +75,7 @@ def payload_must_be_hex(cls, value: bytes) -> bytes:
         """Convert the payload from hexadecimal to bytes"""
         return bytes_from_hex(value.decode())
 
-    @model_validator(mode="after")
+    @model_validator(mode="after")  # type: ignore
     def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         """Check that the token has not expired"""
         payload: bytes = values.payload
@@ -87,7 +87,7 @@ def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
 
         return values
 
-    @model_validator(mode="after")
+    @model_validator(mode="after")  # type: ignore
     def check_signature(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader:
         signature: bytes = values.signature
         payload: bytes = values.payload

From cf206562de2c53b2ca5b76919a5e3c3ee4d8b28a Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 15 Jan 2025 11:24:32 +0900
Subject: [PATCH 29/43] fix: Changing version of aleph-message

---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index 6b084542..7029a6d0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -134,6 +134,7 @@ dependencies = [
   "httpx",
   "secp256k1",
 ]
+
 [tool.hatch.envs.testing.scripts]
 test = "pytest {args:} ./src/ ./tests/ ./examples/"
 test-cov = "pytest --cov {args:} ./src/ ./tests/ ./examples/"

From 10f01cd0fca6b9411ca79ac7105e0154ff388958 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 15 Jan 2025 11:27:22 +0900
Subject: [PATCH 30/43] style: Missing type for URL

---
 pyproject.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 7029a6d0..6b084542 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -134,7 +134,6 @@ dependencies = [
   "httpx",
   "secp256k1",
 ]
-
 [tool.hatch.envs.testing.scripts]
 test = "pytest {args:} ./src/ ./tests/ ./examples/"
 test-cov = "pytest --cov {args:} ./src/ ./tests/ ./examples/"

From 1db00a819ebd03014ef180718bda0471c82ac13d Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 21 Jan 2025 20:11:24 +0900
Subject: [PATCH 31/43] fix: Changing version of aleph-message and fix mypy

Changing the version from the branch to the main of aleph-message
mypy rose some errors about missing name argument, so setting the as None because
they are optional
---
 src/aleph/sdk/types.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py
index cf23f19d..3f9781e3 100644
--- a/src/aleph/sdk/types.py
+++ b/src/aleph/sdk/types.py
@@ -100,3 +100,7 @@ class TokenType(str, Enum):
 
     GAS = "GAS"
     ALEPH = "ALEPH"
+    filename: Optional[str] = Field(default=None)
+    hash: Optional[str] = Field(default=None)
+    url: Optional[str] = Field(default=None)
+    error: Optional[str] = Field(default=None)

From 3a1822750f7ac1a8007d9f78ceb5dd603bd0b054 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 21 Jan 2025 22:13:24 +0900
Subject: [PATCH 32/43] fix: Changing version of aleph-message

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 6b084542..ed7a4e8c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -81,7 +81,7 @@ optional-dependencies.substrate = [
   "substrate-interface",
 ]
 optional-dependencies.tezos = [
-  "aleph-pytezos==3.13.4",
+  "aleph-pytezos==0.1.1",
   "pynacl",
 ]
 urls.Documentation = "https://aleph.im/"

From dd80a664f522779c5d56824f0851fee6e447e67d Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 25 Feb 2025 23:59:31 +0900
Subject: [PATCH 33/43] Fix: Missing pydantic_core and wrong version of tezos

---
 pyproject.toml | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index ed7a4e8c..7bc3cc55 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,15 +34,16 @@ dependencies = [
   "aleph-superfluid>=0.2.1",
   "base58==2.1.1",                                                                                                # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
-  "coincurve>=19.0.0; python_version>='3.11'",
-  "eth-abi>=4.0.0; python_version>='3.11'",
+  "coincurve>=19; python_version>='3.11'",
+  "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
+  "pydantic-core>=2",
   "pydantic-settings>=2",
   "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
-  "web3==6.3.0",
+  "web3==6.3",
 ]
 
 optional-dependencies.all = [
@@ -81,7 +82,7 @@ optional-dependencies.substrate = [
   "substrate-interface",
 ]
 optional-dependencies.tezos = [
-  "aleph-pytezos==0.1.1",
+  "aleph-pytezos==3.13.4",
   "pynacl",
 ]
 urls.Documentation = "https://aleph.im/"
@@ -155,6 +156,7 @@ dependencies = [
   "ruff==0.4.8",
   "isort==5.13.2",
   "pyproject-fmt==2.2.1",
+  "pydantic-core>=2",
   "pydantic-settings>=2",
 ]
 [tool.hatch.envs.linting.scripts]

From 1524a3edd8fa412d5910c47926804d6c70e2ddf3 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Tue, 25 Feb 2025 23:59:49 +0900
Subject: [PATCH 34/43] Fix: Access to PersistentVolumeSizeMib is incompatible
 after migrating to Pydantic2

Using model_validate to access it
---
 src/aleph/sdk/utils.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index e933440a..6e01d49c 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -28,6 +28,7 @@
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
 
+import pydantic_core
 from aleph_message.models import (
     Chain,
     InstanceContent,
@@ -504,7 +505,9 @@ def make_instance_content(
                 ref=ItemHash(rootfs),
                 use_latest=True,
             ),
-            size_mib=PersistentVolumeSizeMib(rootfs_size),
+            size_mib=PersistentVolumeSizeMib.model_validate(
+                {"size_mib": rootfs_size}
+            ).size_mib,
             persistence=VolumePersistence.host,
         ),
         volumes=[parse_volume(volume) for volume in volumes],

From 0a2cff2d0df6053cc1d33c49cfed767d594d728e Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 26 Feb 2025 00:04:05 +0900
Subject: [PATCH 35/43] Fix: Wrong name given to the variable

---
 tests/unit/test_price.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/tests/unit/test_price.py b/tests/unit/test_price.py
index 830fb931..b596c33b 100644
--- a/tests/unit/test_price.py
+++ b/tests/unit/test_price.py
@@ -11,11 +11,11 @@ async def test_get_program_price_valid():
     Test that the get_program_price method returns the correct PriceResponse
     when given a valid item hash.
     """
-    expected = PriceResponse(
-        required_tokens=3.0555555555555556e-06,
-        payment_type="superfluid",
-    )
-    mock_session = make_mock_get_session(expected.dict())
+    expected_response = {
+        "required_tokens": 3.0555555555555556e-06,
+        "payment_type": "superfluid",
+    }
+    mock_session = make_mock_get_session(expected_response)
     async with mock_session:
         response = await mock_session.get_program_price("cacacacacacaca")
         assert response == PriceResponse(**expected_response)  # type: ignore

From 2af612f054c97541e7ab1c0e7d9939e7f4509d64 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Wed, 26 Feb 2025 00:19:20 +0900
Subject: [PATCH 36/43] Style: isort

---
 src/aleph/sdk/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index a1d1aa43..fbac4758 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -27,8 +27,8 @@
 )
 from uuid import UUID
 from zipfile import BadZipFile, ZipFile
-import pydantic_core
 
+import pydantic_core
 from aleph_message.models import (
     Chain,
     InstanceContent,

From 6ded6577c296dfe10b1a34cb3b2b134951bf4224 Mon Sep 17 00:00:00 2001
From: Antonyjin <antony.jin@epitech.eu>
Date: Fri, 14 Mar 2025 23:54:39 +0900
Subject: [PATCH 37/43] Fix: PersistentVolumeSizeMib no longer exist

This class has been deleted from aleph_message and the size is now inside
the PersistentVolume class
---
 src/aleph/sdk/utils.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index fbac4758..1c7c4ee9 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -57,7 +57,7 @@
 from aleph_message.models.execution.volume import (
     MachineVolume,
     ParentVolume,
-    PersistentVolumeSizeMib,
+    PersistentVolume,
     VolumePersistence,
 )
 from aleph_message.utils import Mebibytes
@@ -503,7 +503,7 @@ def make_instance_content(
                 ref=ItemHash(rootfs),
                 use_latest=True,
             ),
-            size_mib=PersistentVolumeSizeMib.model_validate(
+            size_mib=PersistentVolume.model_validate(
                 {"size_mib": rootfs_size}
             ).size_mib,
             persistence=VolumePersistence.host,

From 3b96812c0ee8ad2b82cff0db7f34a57e2ae8f18f Mon Sep 17 00:00:00 2001
From: "Andres D. Molins" <amolinsdiaz@yahoo.es>
Date: Tue, 8 Apr 2025 16:59:30 +0200
Subject: [PATCH 38/43] Fix: Update last `aleph-message` version and use again
 `PersistentVolumeSizeMib` class

---
 pyproject.toml         | 10 +++++-----
 src/aleph/sdk/utils.py |  6 ++----
 2 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 56dd95bd..691596fd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,17 +30,17 @@ dynamic = [ "version" ]
 dependencies = [
   "aiohttp>=3.8.3",
   "aioresponses>=0.7.6",
-  "aleph-message>=1.0.0",
+  "aleph-message>=1",
   "aleph-superfluid>=0.2.1",
-  "base58==2.1.1",                                                                                               # Needed now as default with _load_account changement
+  "base58==2.1.1",                         # Needed now as default with _load_account changement
   "coincurve; python_version<'3.11'",
   "coincurve>=19; python_version>='3.11'",
   "eth-abi>=4; python_version>='3.11'",
   "eth-typing==4.3.1",
   "jwcrypto==1.5.6",
-  "pydantic>=2,<3.0",
+  "pydantic>=2,<3",
   "pydantic-settings>=2",
-  "pynacl==1.5",                                                                                                  # Needed now as default with _load_account changement
+  "pynacl==1.5",                           # Needed now as default with _load_account changement
   "python-magic",
   "typing-extensions",
   "web3==6.3",
@@ -63,7 +63,7 @@ optional-dependencies.encryption = [
   "eciespy>=0.3.13; python_version>='3.11'",
 ]
 optional-dependencies.ledger = [
-  "ledgereth==0.9.1",
+  "ledgereth==0.10",
 ]
 optional-dependencies.mqtt = [
   "aiomqtt<=0.1.3",
diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py
index 1c7c4ee9..31b2be8d 100644
--- a/src/aleph/sdk/utils.py
+++ b/src/aleph/sdk/utils.py
@@ -57,7 +57,7 @@
 from aleph_message.models.execution.volume import (
     MachineVolume,
     ParentVolume,
-    PersistentVolume,
+    PersistentVolumeSizeMib,
     VolumePersistence,
 )
 from aleph_message.utils import Mebibytes
@@ -503,9 +503,7 @@ def make_instance_content(
                 ref=ItemHash(rootfs),
                 use_latest=True,
             ),
-            size_mib=PersistentVolume.model_validate(
-                {"size_mib": rootfs_size}
-            ).size_mib,
+            size_mib=PersistentVolumeSizeMib(rootfs_size),
             persistence=VolumePersistence.host,
         ),
         volumes=[parse_volume(volume) for volume in volumes],

From 768dc3517eca3552ae96ab25b2d8e18ea8dc5869 Mon Sep 17 00:00:00 2001
From: 1yam <lyam.gomes@epitech.eu>
Date: Wed, 9 Apr 2025 13:40:29 +0000
Subject: [PATCH 39/43] fix: invalid signature cause by `0x` + signature.hex()

---
 src/aleph/sdk/chains/common.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/aleph/sdk/chains/common.py b/src/aleph/sdk/chains/common.py
index 8f57f9b4..0a90183c 100644
--- a/src/aleph/sdk/chains/common.py
+++ b/src/aleph/sdk/chains/common.py
@@ -72,7 +72,7 @@ async def sign_message(self, message: Dict) -> Dict:
         """
         message = self._setup_sender(message)
         signature = await self.sign_raw(get_verification_buffer(message))
-        message["signature"] = "0x" + signature.hex()
+        message["signature"] = signature.hex()
         return message
 
     @abstractmethod

From 6c597bbe1792be1794ed939c264e3d9f69d1f579 Mon Sep 17 00:00:00 2001
From: 1yam <lyam.gomes@epitech.eu>
Date: Wed, 9 Apr 2025 14:10:20 +0000
Subject: [PATCH 40/43] fix: add '0x' to the signature if not here (error
 happenings only on unit test)

---
 src/aleph/sdk/chains/common.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/aleph/sdk/chains/common.py b/src/aleph/sdk/chains/common.py
index 0a90183c..bcb7bbfa 100644
--- a/src/aleph/sdk/chains/common.py
+++ b/src/aleph/sdk/chains/common.py
@@ -73,6 +73,10 @@ async def sign_message(self, message: Dict) -> Dict:
         message = self._setup_sender(message)
         signature = await self.sign_raw(get_verification_buffer(message))
         message["signature"] = signature.hex()
+
+        if not str(message["signature"]).startswith("0x"):
+            message["signature"] = "0x" + message["signature"]
+
         return message
 
     @abstractmethod

From 5c5ccd57799d82ae45b7195733f6514dd34d4703 Mon Sep 17 00:00:00 2001
From: "Andres D. Molins" <nesitor@gmail.com>
Date: Wed, 9 Apr 2025 18:45:43 +0200
Subject: [PATCH 41/43] Refactor: Apply the `.hex()` quick fix on the
 ETHAccount class instead on the base one as other chains can be affected.

---
 src/aleph/sdk/chains/common.py   |  3 ---
 src/aleph/sdk/chains/ethereum.py | 18 +++++++++++++++++-
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/aleph/sdk/chains/common.py b/src/aleph/sdk/chains/common.py
index bcb7bbfa..d2714d62 100644
--- a/src/aleph/sdk/chains/common.py
+++ b/src/aleph/sdk/chains/common.py
@@ -74,9 +74,6 @@ async def sign_message(self, message: Dict) -> Dict:
         signature = await self.sign_raw(get_verification_buffer(message))
         message["signature"] = signature.hex()
 
-        if not str(message["signature"]).startswith("0x"):
-            message["signature"] = "0x" + message["signature"]
-
         return message
 
     @abstractmethod
diff --git a/src/aleph/sdk/chains/ethereum.py b/src/aleph/sdk/chains/ethereum.py
index c185d174..863e2bbf 100644
--- a/src/aleph/sdk/chains/ethereum.py
+++ b/src/aleph/sdk/chains/ethereum.py
@@ -2,7 +2,7 @@
 import base64
 from decimal import Decimal
 from pathlib import Path
-from typing import Awaitable, Optional, Union
+from typing import Awaitable, Dict, Optional, Union
 
 from aleph_message.models import Chain
 from eth_account import Account  # type: ignore
@@ -80,6 +80,22 @@ async def sign_raw(self, buffer: bytes) -> bytes:
         sig = self._account.sign_message(msghash)
         return sig["signature"]
 
+    async def sign_message(self, message: Dict) -> Dict:
+        """
+        Returns a signed message from an aleph.im message.
+        Args:
+            message: Message to sign
+        Returns:
+            Dict: Signed message
+        """
+        signed_message = await super().sign_message(message)
+
+        # Apply that fix as seems that sometimes the .hex() method doesn't add the 0x str at the beginning
+        if not str(signed_message["signature"]).startswith("0x"):
+            signed_message["signature"] = "0x" + signed_message["signature"]
+
+        return signed_message
+
     def connect_chain(self, chain: Optional[Chain] = None):
         self.chain = chain
         if self.chain:

From 1562f26489e1ba7081eb514ac213b6887e2635fc Mon Sep 17 00:00:00 2001
From: 1yam <lyam.gomes@epitech.eu>
Date: Thu, 10 Apr 2025 11:48:43 +0000
Subject: [PATCH 42/43] fix: pydantic model should use `.model_dump()` instead
 of `dict()`

---
 src/aleph/sdk/client/authenticated_http.py | 2 +-
 src/aleph/sdk/client/http.py               | 2 +-
 tests/unit/test_price.py                   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py
index 2ae0d0a0..2975e112 100644
--- a/src/aleph/sdk/client/authenticated_http.py
+++ b/src/aleph/sdk/client/authenticated_http.py
@@ -525,7 +525,7 @@ async def create_instance(
         )
 
         message, status, response = await self.submit(
-            content=content.dict(exclude_none=True),
+            content=content.model_dump(exclude_none=True),
             message_type=MessageType.instance,
             channel=channel,
             storage_engine=storage_engine,
diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py
index 6d2b42c7..885158b3 100644
--- a/src/aleph/sdk/client/http.py
+++ b/src/aleph/sdk/client/http.py
@@ -462,7 +462,7 @@ async def get_estimated_price(
         self,
         content: ExecutableContent,
     ) -> PriceResponse:
-        cleaned_content = content.dict(exclude_none=True)
+        cleaned_content = content.model_dump(exclude_none=True)
         item_content: str = json.dumps(
             cleaned_content,
             separators=(",", ":"),
diff --git a/tests/unit/test_price.py b/tests/unit/test_price.py
index fe9e3468..e60680f8 100644
--- a/tests/unit/test_price.py
+++ b/tests/unit/test_price.py
@@ -15,7 +15,7 @@ async def test_get_program_price_valid():
         required_tokens=3.0555555555555556e-06,
         payment_type="superfluid",
     )
-    mock_session = make_mock_get_session(expected.dict())
+    mock_session = make_mock_get_session(expected.model_dump())
     async with mock_session:
         response = await mock_session.get_program_price("cacacacacacaca")
         assert response == expected

From fde7c35c80942c91dba40ca469b211be91b9d2ec Mon Sep 17 00:00:00 2001
From: 1yam <lyam.gomes@epitech.eu>
Date: Thu, 10 Apr 2025 11:55:49 +0000
Subject: [PATCH 43/43] fix: add dummy signature for unauthenticated price
 estimates

When estimating prices without authentication, there's no valid signature available.
This fix uses a dummy signature so that message validation passes in these cases.
---
 src/aleph/sdk/client/http.py | 33 +++++++++++++++++----------------
 1 file changed, 17 insertions(+), 16 deletions(-)

diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py
index 885158b3..3d42d490 100644
--- a/src/aleph/sdk/client/http.py
+++ b/src/aleph/sdk/client/http.py
@@ -468,24 +468,25 @@ async def get_estimated_price(
             separators=(",", ":"),
             default=extended_json_encoder,
         )
-        message = parse_message(
-            dict(
-                sender=content.address,
-                chain=Chain.ETH,
-                type=(
-                    MessageType.program
-                    if isinstance(content, ProgramContent)
-                    else MessageType.instance
-                ),
-                content=cleaned_content,
-                item_content=item_content,
-                time=time.time(),
-                channel=settings.DEFAULT_CHANNEL,
-                item_type=ItemType.inline,
-                item_hash=compute_sha256(item_content),
-            )
+        message_dict = dict(
+            sender=content.address,
+            chain=Chain.ETH,
+            type=(
+                MessageType.program
+                if isinstance(content, ProgramContent)
+                else MessageType.instance
+            ),
+            content=cleaned_content,
+            item_content=item_content,
+            time=time.time(),
+            channel=settings.DEFAULT_CHANNEL,
+            item_type=ItemType.inline,
+            item_hash=compute_sha256(item_content),
+            signature="0x" + "0" * 130,  # Add a dummy signature to pass validation
         )
 
+        message = parse_message(message_dict)
+
         async with self.http_session.post(
             "/api/v0/price/estimate", json=dict(message=message)
         ) as resp: