diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8dfa57d88752957c015b83103664f2dc29e71721..648a89363ca2c3bf1c101f95e84b935eb23007c6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -108,11 +108,6 @@ pre-commit:hooks:
     - poetry install
     - poetry run pytest
 
-tests:3.8:
-  extends: .tests
-  variables:
-    PYTHON_VERSION: "3.8"
-
 tests:3.9:
   extends: .tests
   variables:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4b214b83aa19d7763bf127f111f3fd72abc4cd90..ef7f75990004cd2475eca04ef6e8ead1914942d1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -34,7 +34,7 @@ repos:
     rev: v3.15.0
     hooks:
     - id: pyupgrade
-      args: [--py38-plus]
+      args: [--py39-plus]
 -   repo: https://github.com/Lucas-C/pre-commit-hooks
     rev: v1.5.4
     hooks:
diff --git a/README.md b/README.md
index 82ed31e0e19c5222c749342f3cd1f7b4e2fb5661..757cf61cce7be1a8a0dfd600324c8876f7b8d08e 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ It is currently used by following programs:
 
 ## Requirements
 
-- Python >= 3.8.0
+- Python >= 3.9.0
 - [graphql-core](https://pypi.org/project/graphql-core)
 - [websocket-client](https://pypi.org/project/websocket-client)
 - [jsonschema](https://pypi.org/project/jsonschema)
diff --git a/docs/index.rst b/docs/index.rst
index 2468ca16423d3814f19d1e8e66667981ae2c69be..aa103b3b88239fc8b22ca637ad13137adcbd3d6c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,7 +16,7 @@ DuniterPy helps to handle the following problem:
 Requirements
 ------------
 
-DuniterPy requires Python 3.8.0 minimum.
+DuniterPy requires Python 3.9.0 minimum.
 
 Installation
 ------------
diff --git a/duniterpy/api/endpoint.py b/duniterpy/api/endpoint.py
index 4fb0fd08aa1143a4799ae17c9f7ca7057d181bac..c4eb05342760191689323348cf762af61da8759f 100644
--- a/duniterpy/api/endpoint.py
+++ b/duniterpy/api/endpoint.py
@@ -15,7 +15,7 @@
 
 import re
 from ipaddress import ip_address
-from typing import Any, Dict, Optional, Tuple, Type, TypeVar
+from typing import Any, Dict, Optional, TypeVar
 
 from duniterpy import constants as const
 
@@ -61,7 +61,7 @@ EndpointType = TypeVar("EndpointType", bound="Endpoint")
 
 class Endpoint:
     @classmethod
-    def from_inline(cls: Type[EndpointType], inline: str) -> EndpointType:
+    def from_inline(cls: type[EndpointType], inline: str) -> EndpointType:
         raise NotImplementedError("from_inline(..) is not implemented")
 
     def inline(self) -> str:
@@ -89,7 +89,7 @@ class UnknownEndpoint(Endpoint):
         self.properties = properties
 
     @classmethod
-    def from_inline(cls: Type[UnknownEndpointType], inline: str) -> UnknownEndpointType:
+    def from_inline(cls: type[UnknownEndpointType], inline: str) -> UnknownEndpointType:
         """
         Return UnknownEndpoint instance from endpoint string
 
@@ -161,7 +161,7 @@ class BMAEndpoint(Endpoint):
         self.port = port
 
     @classmethod
-    def from_inline(cls: Type[BMAEndpointType], inline: str) -> BMAEndpointType:
+    def from_inline(cls: type[BMAEndpointType], inline: str) -> BMAEndpointType:
         """
         Return BMAEndpoint instance from endpoint string
 
@@ -252,7 +252,7 @@ class SecuredBMAEndpoint(BMAEndpoint):
 
     @classmethod
     def from_inline(
-        cls: Type[SecuredBMAEndpointType], inline: str
+        cls: type[SecuredBMAEndpointType], inline: str
     ) -> SecuredBMAEndpointType:
         """
         Return SecuredBMAEndpoint instance from endpoint string
@@ -325,7 +325,7 @@ class WS2PEndpoint(Endpoint):
         self.path = path
 
     @classmethod
-    def from_inline(cls: Type[WS2PEndpointType], inline: str) -> WS2PEndpointType:
+    def from_inline(cls: type[WS2PEndpointType], inline: str) -> WS2PEndpointType:
         """
         Return WS2PEndpoint instance from endpoint string
 
@@ -402,7 +402,7 @@ class ESCoreEndpoint(Endpoint):
         self.port = port
 
     @classmethod
-    def from_inline(cls: Type[ESCoreEndpointType], inline: str) -> ESCoreEndpointType:
+    def from_inline(cls: type[ESCoreEndpointType], inline: str) -> ESCoreEndpointType:
         """
         Return ESCoreEndpoint instance from endpoint string
 
@@ -461,7 +461,7 @@ class ESUserEndpoint(Endpoint):
         self.port = port
 
     @classmethod
-    def from_inline(cls: Type[ESUserEndpointType], inline: str) -> ESUserEndpointType:
+    def from_inline(cls: type[ESUserEndpointType], inline: str) -> ESUserEndpointType:
         """
         Return ESUserEndpoint instance from endpoint string
 
@@ -523,7 +523,7 @@ class ESSubscribtionEndpoint(Endpoint):
 
     @classmethod
     def from_inline(
-        cls: Type[ESSubscribtionEndpointType], inline: str
+        cls: type[ESSubscribtionEndpointType], inline: str
     ) -> ESSubscribtionEndpointType:
         """
         Return ESSubscribtionEndpoint instance from endpoint string
@@ -604,7 +604,7 @@ class GVAEndpoint(Endpoint):
         self.path = path
 
     @classmethod
-    def from_inline(cls: Type[GVAEndpointType], inline: str) -> GVAEndpointType:
+    def from_inline(cls: type[GVAEndpointType], inline: str) -> GVAEndpointType:
         """
         Return GVAEndpoint instance from endpoint string
 
@@ -729,7 +729,7 @@ def endpoint(value: Any) -> Any:
     return result
 
 
-def fix_host_ipv4_mix_up(host: str, ipv4: str) -> Tuple[str, str]:
+def fix_host_ipv4_mix_up(host: str, ipv4: str) -> tuple[str, str]:
     mixed_up = False
     try:
         mixed_up = ip_address(host).version == 4 and not ipv4
diff --git a/duniterpy/documents/block.py b/duniterpy/documents/block.py
index 52c9c8c0e9d3a5a7c07579ed1066b055fe9a2c0a..e199a33d19a44f60a251fbf1e6812768fef7c7bf 100644
--- a/duniterpy/documents/block.py
+++ b/duniterpy/documents/block.py
@@ -16,7 +16,8 @@
 import base64
 import hashlib
 import re
-from typing import List, Optional, Sequence, Type, TypeVar
+from collections.abc import Sequence
+from typing import Optional, TypeVar
 
 from ..constants import BLOCK_HASH_REGEX, G1_CURRENCY_CODENAME, PUBKEY_REGEX
 
@@ -162,14 +163,14 @@ class Block(Document):
         prev_issuer: Optional[str],
         parameters: Optional[Sequence[str]],
         members_count: int,
-        identities: List[Identity],
-        joiners: List[Membership],
-        actives: List[Membership],
-        leavers: List[Membership],
-        revokations: List[Revocation],
-        excluded: List[str],
-        certifications: List[Certification],
-        transactions: List[Transaction],
+        identities: list[Identity],
+        joiners: list[Membership],
+        actives: list[Membership],
+        leavers: list[Membership],
+        revokations: list[Revocation],
+        excluded: list[str],
+        certifications: list[Certification],
+        transactions: list[Transaction],
         inner_hash: str,
         nonce: int,
         signing_key: Optional[SigningKey] = None,
@@ -258,7 +259,7 @@ class Block(Document):
         return BlockID(self.number, self.proof_of_work())
 
     @classmethod
-    def from_parsed_json(cls: Type[BlockType], parsed_json_block: dict) -> BlockType:
+    def from_parsed_json(cls: type[BlockType], parsed_json_block: dict) -> BlockType:
         """
         Return a Block instance from the python dict produced when parsing json
 
@@ -351,7 +352,7 @@ class Block(Document):
         return block
 
     @classmethod
-    def from_signed_raw(cls: Type[BlockType], signed_raw: str) -> BlockType:
+    def from_signed_raw(cls: type[BlockType], signed_raw: str) -> BlockType:
         """
         Create Block instance from signed raw format string
 
diff --git a/duniterpy/documents/block_id.py b/duniterpy/documents/block_id.py
index d415fe26b089856fa606e192e11f5962667b2c55..5371417d7c5ab32e3ea57c063eed9b9ce92195e0 100644
--- a/duniterpy/documents/block_id.py
+++ b/duniterpy/documents/block_id.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from typing import Type, TypeVar, Union
+from typing import TypeVar, Union
 
 from ..constants import BLOCK_HASH_REGEX, BLOCK_NUMBER_REGEX, EMPTY_HASH
 from .document import MalformedDocumentError
@@ -38,11 +38,11 @@ class BlockID:
         self.sha_hash = sha_hash
 
     @classmethod
-    def empty(cls: Type[BlockIDType]) -> BlockIDType:
+    def empty(cls: type[BlockIDType]) -> BlockIDType:
         return cls(0, EMPTY_HASH)
 
     @classmethod
-    def from_str(cls: Type[BlockIDType], blockid: str) -> BlockIDType:
+    def from_str(cls: type[BlockIDType], blockid: str) -> BlockIDType:
         """
         :param blockid: The block id
         """
diff --git a/duniterpy/documents/certification.py b/duniterpy/documents/certification.py
index 766f8597ec5cce764f52adeaeda27ddef1aaa729..90fbe5b382bb957b1062be8986fc89d10f29e3ae 100644
--- a/duniterpy/documents/certification.py
+++ b/duniterpy/documents/certification.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from typing import Any, Optional, Type, TypeVar, Union
+from typing import Any, Optional, TypeVar, Union
 
 from ..constants import (
     BLOCK_ID_REGEX,
@@ -107,7 +107,7 @@ class Certification(Document):
 
     @classmethod
     def from_signed_raw(
-        cls: Type[CertificationType], signed_raw: str
+        cls: type[CertificationType], signed_raw: str
     ) -> CertificationType:
         """
         Return Certification instance from signed raw document
@@ -149,7 +149,7 @@ class Certification(Document):
 
     @classmethod
     def from_inline(
-        cls: Type[CertificationType],
+        cls: type[CertificationType],
         block_hash: Optional[str],
         inline: str,
         version: int = VERSION,
diff --git a/duniterpy/documents/document.py b/duniterpy/documents/document.py
index f7856641c4aa415e6c0f26e2ed0358aadfdf1fd3..4f90137fe89151ec1311872b77065e0371f54205 100644
--- a/duniterpy/documents/document.py
+++ b/duniterpy/documents/document.py
@@ -17,7 +17,8 @@ import base64
 import hashlib
 import logging
 import re
-from typing import Any, Dict, Optional, Pattern, Type, TypeVar
+from re import Pattern
+from typing import Any, Optional, TypeVar
 
 from ..constants import SIGNATURE_REGEX
 from ..key import SigningKey, VerifyingKey
@@ -50,7 +51,7 @@ class Document:
     re_currency = re.compile("Currency: ([^\n]+)\n")
     re_signature = re.compile(f"({SIGNATURE_REGEX})\n")
 
-    fields_parsers: Dict[str, Pattern] = {
+    fields_parsers: dict[str, Pattern] = {
         "Version": re_version,
         "Currency": re_currency,
         "Signature": re_signature,
@@ -89,7 +90,7 @@ class Document:
         )
 
     @classmethod
-    def parse_field(cls: Type[DocumentType], field_name: str, line: str) -> Any:
+    def parse_field(cls: type[DocumentType], field_name: str, line: str) -> Any:
         """
         Parse a document field with regular expression and return the value
 
diff --git a/duniterpy/documents/identity.py b/duniterpy/documents/identity.py
index 3f9ca96441492d07ea1d95a51857ff5a33559c08..42f1797f7456d172e872a63ba96560b58ce6ee7b 100644
--- a/duniterpy/documents/identity.py
+++ b/duniterpy/documents/identity.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from typing import Any, Optional, Type, TypeVar
+from typing import Any, Optional, TypeVar
 
 from ..constants import (
     BLOCK_ID_REGEX,
@@ -127,7 +127,7 @@ class Identity(Document):
 
     @classmethod
     def from_inline(
-        cls: Type[IdentityType],
+        cls: type[IdentityType],
         inline: str,
         version: int = VERSION,
         currency: str = G1_CURRENCY_CODENAME,
@@ -155,7 +155,7 @@ class Identity(Document):
         return identity
 
     @classmethod
-    def from_signed_raw(cls: Type[IdentityType], signed_raw: str) -> IdentityType:
+    def from_signed_raw(cls: type[IdentityType], signed_raw: str) -> IdentityType:
         """
         Return Identity instance from a signed_raw string
 
@@ -214,7 +214,7 @@ Timestamp: {self.block_id}\n"
 
     @classmethod
     def from_certification_raw(
-        cls: Type[IdentityType], certification_raw: str
+        cls: type[IdentityType], certification_raw: str
     ) -> IdentityType:
         """
         Return Identity instance from certification_raw
@@ -248,7 +248,7 @@ Timestamp: {self.block_id}\n"
 
     @classmethod
     def from_revocation_raw(
-        cls: Type[IdentityType], revocation_raw: str
+        cls: type[IdentityType], revocation_raw: str
     ) -> IdentityType:
         """
         Return Identity instance from revocation_raw
@@ -282,7 +282,7 @@ Timestamp: {self.block_id}\n"
 
     @classmethod
     def from_bma_lookup_response(
-        cls: Type[IdentityType],
+        cls: type[IdentityType],
         currency: str,
         pubkey: str,
         lookup_response: dict,
diff --git a/duniterpy/documents/membership.py b/duniterpy/documents/membership.py
index 214dd4e91ef30d36c15e32087704441d085a0955..2677421420f8b534895687ae895e18b758440a2b 100644
--- a/duniterpy/documents/membership.py
+++ b/duniterpy/documents/membership.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from typing import Any, Optional, Type, TypeVar
+from typing import Any, Optional, TypeVar
 
 from ..constants import (
     BLOCK_ID_REGEX,
@@ -136,7 +136,7 @@ class Membership(Document):
 
     @classmethod
     def from_inline(
-        cls: Type[MembershipType],
+        cls: type[MembershipType],
         inline: str,
         version: int = VERSION,
         currency: str = G1_CURRENCY_CODENAME,
@@ -174,7 +174,7 @@ class Membership(Document):
         return membership
 
     @classmethod
-    def from_signed_raw(cls: Type[MembershipType], signed_raw: str) -> MembershipType:
+    def from_signed_raw(cls: type[MembershipType], signed_raw: str) -> MembershipType:
         """
         Return Membership instance from signed raw format
 
diff --git a/duniterpy/documents/peer.py b/duniterpy/documents/peer.py
index a4bc301b3306a1ed4e017b2ffd59d728096b27b8..39b6c415e88f31a107452717962a1eb4ebc14cd5 100644
--- a/duniterpy/documents/peer.py
+++ b/duniterpy/documents/peer.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from typing import List, Optional, Type, TypeVar
+from typing import Optional, TypeVar
 
 from duniterpy.api.endpoint import Endpoint, endpoint
 
@@ -66,7 +66,7 @@ class Peer(Document):
         self,
         pubkey: str,
         block_id: BlockID,
-        endpoints: List[Endpoint],
+        endpoints: list[Endpoint],
         signing_key: Optional[SigningKey] = None,
         version: int = VERSION,
         currency: str = G1_CURRENCY_CODENAME,
@@ -85,13 +85,13 @@ class Peer(Document):
 
         self.pubkey = pubkey
         self.block_id = block_id
-        self.endpoints: List[Endpoint] = endpoints
+        self.endpoints: list[Endpoint] = endpoints
 
         if signing_key is not None:
             self.sign(signing_key)
 
     @classmethod
-    def from_signed_raw(cls: Type[PeerType], raw: str) -> PeerType:
+    def from_signed_raw(cls: type[PeerType], raw: str) -> PeerType:
         """
         Return a Peer instance from a signed raw format string
 
@@ -154,7 +154,7 @@ Endpoints:\n"
         return doc
 
     @classmethod
-    def from_bma(cls: Type[PeerType], data: dict) -> PeerType:
+    def from_bma(cls: type[PeerType], data: dict) -> PeerType:
         # get Peer Document from bma dict
         version = data["version"]
         currency = data["currency"]
diff --git a/duniterpy/documents/revocation.py b/duniterpy/documents/revocation.py
index dcb82813af2c316c5122b2a506396a0eca820da5..4aab07588d68429690318d20048fbd0c5de6a18d 100644
--- a/duniterpy/documents/revocation.py
+++ b/duniterpy/documents/revocation.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from typing import Any, Optional, Type, TypeVar, Union
+from typing import Any, Optional, TypeVar, Union
 
 from ..constants import (
     BLOCK_ID_REGEX,
@@ -99,7 +99,7 @@ class Revocation(Document):
 
     @classmethod
     def from_inline(
-        cls: Type[RevocationType],
+        cls: type[RevocationType],
         inline: str,
         version: int = VERSION,
         currency: str = G1_CURRENCY_CODENAME,
@@ -127,7 +127,7 @@ class Revocation(Document):
         return revocation
 
     @classmethod
-    def from_signed_raw(cls: Type[RevocationType], signed_raw: str) -> RevocationType:
+    def from_signed_raw(cls: type[RevocationType], signed_raw: str) -> RevocationType:
         """
         Return Revocation document instance from a signed raw string
 
diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py
index 9ef56cc52c1e0e74272adc2ae96e497f695f3251..6ac1577148c40ab7c9a648de1b649e5db1dfbbf7 100644
--- a/duniterpy/documents/transaction.py
+++ b/duniterpy/documents/transaction.py
@@ -16,7 +16,7 @@
 import base64
 import logging
 import re
-from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union
+from typing import Any, Optional, TypeVar, Union
 
 import pypeg2
 
@@ -37,7 +37,7 @@ from .document import Document, MalformedDocumentError
 VERSION = 10
 
 
-def reduce_base(amount: int, base: int) -> Tuple[int, int]:
+def reduce_base(amount: int, base: int) -> tuple[int, int]:
     """
     Compute the reduced base of the given parameters
 
@@ -119,7 +119,7 @@ class InputSource:
         return hash((self.amount, self.base, self.source, self.origin_id, self.index))
 
     @classmethod
-    def from_inline(cls: Type[InputSourceType], inline: str) -> InputSourceType:
+    def from_inline(cls: type[InputSourceType], inline: str) -> InputSourceType:
         """
         Return Transaction instance from inline string format
 
@@ -191,7 +191,7 @@ class OutputSource:
         return hash((self.amount, self.base, self.condition))
 
     @classmethod
-    def from_inline(cls: Type[OutputSourceType], inline: str) -> OutputSourceType:
+    def from_inline(cls: type[OutputSourceType], inline: str) -> OutputSourceType:
         """
         Return OutputSource instance from inline string format
 
@@ -272,7 +272,7 @@ class SIGParameter:
 
     @classmethod
     def from_parameter(
-        cls: Type[SIGParameterType], parameter: str
+        cls: type[SIGParameterType], parameter: str
     ) -> Optional[SIGParameterType]:
         """
         Return a SIGParameter instance from an index parameter
@@ -328,7 +328,7 @@ class XHXParameter:
 
     @classmethod
     def from_parameter(
-        cls: Type[XHXParameterType], parameter: str
+        cls: type[XHXParameterType], parameter: str
     ) -> Optional[XHXParameterType]:
         """
         Return a XHXParameter instance from an index parameter
@@ -362,7 +362,7 @@ UnlockParameterType = TypeVar("UnlockParameterType", bound="UnlockParameter")
 class UnlockParameter:
     @classmethod
     def from_parameter(
-        cls: Type[UnlockParameterType], parameter: str
+        cls: type[UnlockParameterType], parameter: str
     ) -> Optional[Union[SIGParameter, XHXParameter]]:
         """
         Return UnlockParameter instance from parameter string
@@ -397,7 +397,7 @@ class Unlock:
     re_inline = re.compile("([0-9]+):((?:SIG\\([0-9]+\\)|XHX\\([0-9]+\\)|\\s)+)")
 
     def __init__(
-        self, index: int, parameters: List[Union[SIGParameter, XHXParameter]]
+        self, index: int, parameters: list[Union[SIGParameter, XHXParameter]]
     ) -> None:
         """
         Init Unlock instance
@@ -425,7 +425,7 @@ class Unlock:
         return hash((self.index, self.parameters))
 
     @classmethod
-    def from_inline(cls: Type[UnlockType], inline: str) -> UnlockType:
+    def from_inline(cls: type[UnlockType], inline: str) -> UnlockType:
         """
         Return an Unlock instance from inline string format
 
@@ -535,13 +535,13 @@ class Transaction(Document):
         self,
         block_id: Optional[BlockID],
         locktime: int,
-        issuers: List[str],
-        inputs: List[InputSource],
-        unlocks: List[Unlock],
-        outputs: List[OutputSource],
+        issuers: list[str],
+        inputs: list[InputSource],
+        unlocks: list[Unlock],
+        outputs: list[OutputSource],
         comment: str,
         time: Optional[int] = None,
-        signing_keys: Optional[Union[SigningKey, List[SigningKey]]] = None,
+        signing_keys: Optional[Union[SigningKey, list[SigningKey]]] = None,
         version: int = VERSION,
         currency: str = G1_CURRENCY_CODENAME,
     ) -> None:
@@ -569,7 +569,7 @@ class Transaction(Document):
         self.outputs = outputs
         self.comment = comment
         self.time = time
-        self.signatures: List[str] = []
+        self.signatures: list[str] = []
 
         if signing_keys:
             self.multi_sign(signing_keys)
@@ -612,7 +612,7 @@ class Transaction(Document):
 
     @classmethod
     def from_bma_history(
-        cls: Type[TransactionType], tx_data: Dict, currency: str = G1_CURRENCY_CODENAME
+        cls: type[TransactionType], tx_data: dict, currency: str = G1_CURRENCY_CODENAME
     ) -> TransactionType:
         """
         Get the transaction instance from json
@@ -649,7 +649,7 @@ Comment: {comment}
 
     @classmethod
     def from_compact(
-        cls: Type[TransactionType], compact: str, currency: str = G1_CURRENCY_CODENAME
+        cls: type[TransactionType], compact: str, currency: str = G1_CURRENCY_CODENAME
     ) -> TransactionType:
         """
         Return Transaction instance from compact string format
@@ -738,7 +738,7 @@ Comment: {comment}
 
     @classmethod
     def from_signed_raw(
-        cls: Type[TransactionType], raw: str, time: int = 0
+        cls: type[TransactionType], raw: str, time: int = 0
     ) -> TransactionType:
         """
         Return a Transaction instance from a raw string format
@@ -925,7 +925,7 @@ Currency: {self.currency}\n"
         """
         self.multi_sign(key)
 
-    def multi_sign(self, keys: Union[SigningKey, List[SigningKey]]) -> None:
+    def multi_sign(self, keys: Union[SigningKey, list[SigningKey]]) -> None:
         """
         Add signature(s) to the document from one key or a list of multiple keys
 
@@ -948,7 +948,7 @@ Currency: {self.currency}\n"
         """
         return self.check_signatures(pubkey)
 
-    def check_signatures(self, pubkeys: Union[str, List[str]]) -> bool:
+    def check_signatures(self, pubkeys: Union[str, list[str]]) -> bool:
         """
         Check if the signatures matches a public key or a list of public keys
 
@@ -989,11 +989,11 @@ class SimpleTransaction(Transaction):
         locktime: int,
         issuer: str,
         single_input: InputSource,
-        unlocks: List[Unlock],
-        outputs: List[OutputSource],
+        unlocks: list[Unlock],
+        outputs: list[OutputSource],
         comment: str,
         time: int = 0,
-        signing_keys: Optional[Union[SigningKey, List[SigningKey]]] = None,
+        signing_keys: Optional[Union[SigningKey, list[SigningKey]]] = None,
         version: int = VERSION,
         currency: str = G1_CURRENCY_CODENAME,
     ) -> None:
diff --git a/duniterpy/grammars/output.py b/duniterpy/grammars/output.py
index b648a7638f9814d990741b46fb771ae4e077bb3c..398e30b575d6d24351a8ad5161afd8a440b84a16 100644
--- a/duniterpy/grammars/output.py
+++ b/duniterpy/grammars/output.py
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from typing import Any, Optional, Type, TypeVar, Union
+from typing import Any, Optional, TypeVar, Union
 
 from pypeg2 import Enum, K, Keyword, attr, contiguous, maybe_some, re, whitespace
 
@@ -79,7 +79,7 @@ class SIG:
         return hash((self.value, self.pubkey))
 
     @classmethod
-    def token(cls: Type[SIGType], pubkey: str) -> SIGType:
+    def token(cls: type[SIGType], pubkey: str) -> SIGType:
         """
         Return SIG instance from pubkey
 
@@ -139,7 +139,7 @@ class CSV:
         return hash((self.value, self.time))
 
     @classmethod
-    def token(cls: Type[CSVType], time: int) -> CSVType:
+    def token(cls: type[CSVType], time: int) -> CSVType:
         """
         Return CSV instance from time
 
@@ -198,7 +198,7 @@ class CLTV:
         return hash((self.value, self.timestamp))
 
     @classmethod
-    def token(cls: Type[CLTVType], timestamp: int) -> CLTVType:
+    def token(cls: type[CLTVType], timestamp: int) -> CLTVType:
         """
         Return CLTV instance from timestamp
 
@@ -257,7 +257,7 @@ class XHX:
         return hash((self.value, self.sha_hash))
 
     @classmethod
-    def token(cls: Type[XHXType], sha_hash: str) -> XHXType:
+    def token(cls: type[XHXType], sha_hash: str) -> XHXType:
         """
         Return XHX instance from sha_hash
 
@@ -294,7 +294,7 @@ class Operator(Keyword):
     regex = re.compile(r"[&&|\|\||\w]+")
 
     @classmethod
-    def token(cls: Type[OperatorType], keyword: str) -> OperatorType:
+    def token(cls: type[OperatorType], keyword: str) -> OperatorType:
         """
         Return Operator instance from keyword
 
@@ -361,7 +361,7 @@ class Condition:
 
     @classmethod
     def token(
-        cls: Type[ConditionType],
+        cls: type[ConditionType],
         left: Any,
         op: Optional[Any] = None,
         right: Optional[Any] = None,
diff --git a/duniterpy/helpers/network.py b/duniterpy/helpers/network.py
index 692c41d4206f1bc2f323104d0c59f127bc93dccf..047bee6a854bf85bda533e55ec704946538885f0 100644
--- a/duniterpy/helpers/network.py
+++ b/duniterpy/helpers/network.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from itertools import groupby
-from typing import Any, Dict, List
+from typing import Any
 
 from duniterpy.api import bma
 from duniterpy.api.client import Client
@@ -22,7 +22,7 @@ from duniterpy.documents.peer import MalformedDocumentError, Peer
 from duniterpy.documents.ws2p.heads import HeadV2
 
 
-def get_available_nodes(client: Client) -> List[List[Dict[str, Any]]]:
+def get_available_nodes(client: Client) -> list[list[dict[str, Any]]]:
     """
     Get available nodes grouped and sorted by descending block_id
 
diff --git a/duniterpy/key/ascii_armor.py b/duniterpy/key/ascii_armor.py
index c1973d305196dff9472c5f5a316aa56759bd5d5c..0b5952c9e5a4ffac0937f5298b261b8d84552bb1 100644
--- a/duniterpy/key/ascii_armor.py
+++ b/duniterpy/key/ascii_armor.py
@@ -16,7 +16,7 @@
 import base64
 import importlib.metadata
 import re
-from typing import Any, Dict, List, Optional
+from typing import Any, Optional
 
 import libnacl
 
@@ -86,7 +86,7 @@ class AsciiArmor:
     def create(
         message: str,
         pubkey: Optional[str] = None,
-        signing_keys: Optional[List[SigningKey]] = None,
+        signing_keys: Optional[list[SigningKey]] = None,
         message_comment: Optional[str] = None,
         signatures_comment: Optional[str] = None,
     ) -> str:
@@ -260,7 +260,7 @@ class AsciiArmor:
     def parse(
         ascii_armor_message: str,
         signing_key: Optional[SigningKey] = None,
-        sender_pubkeys: Optional[List[str]] = None,
+        sender_pubkeys: Optional[list[str]] = None,
     ) -> dict:
         """
         Return a dict with parsed content (decrypted message, signature validation) ::
@@ -298,7 +298,7 @@ class AsciiArmor:
         parsed_result = {
             "message": {"fields": {}, "content": ""},
             "signatures": [],
-        }  # type: Dict[str, Any]
+        }  # type: dict[str, Any]
         cursor_status = 0
         message = ""
         signatures_index = 0
diff --git a/duniterpy/key/crc_pubkey.py b/duniterpy/key/crc_pubkey.py
index 3f8cbd9d8f5caf301827db4ddb3ad011713ab2fa..b5d69acf84086ffa8205056df443bb039b4251d3 100644
--- a/duniterpy/key/crc_pubkey.py
+++ b/duniterpy/key/crc_pubkey.py
@@ -15,7 +15,7 @@
 
 import hashlib
 import re
-from typing import Type, TypeVar
+from typing import TypeVar
 
 import base58
 
@@ -48,7 +48,7 @@ class CRCPubkey:
         self.crc = crc
 
     @classmethod
-    def from_str(cls: Type[CRCPubkeyType], crc_pubkey: str) -> CRCPubkeyType:
+    def from_str(cls: type[CRCPubkeyType], crc_pubkey: str) -> CRCPubkeyType:
         """
         Return CRCPubkey instance from CRC public key string
 
@@ -63,7 +63,7 @@ class CRCPubkey:
         return cls(pubkey, crc)
 
     @classmethod
-    def from_pubkey(cls: Type[CRCPubkeyType], pubkey: str) -> CRCPubkeyType:
+    def from_pubkey(cls: type[CRCPubkeyType], pubkey: str) -> CRCPubkeyType:
         """
         Return CRCPubkey instance from public key string
 
diff --git a/duniterpy/key/signing_key.py b/duniterpy/key/signing_key.py
index db49f77ccd5bc91fe71c339fae7bac1580d1387b..4e3983acfe9921983592bcbc7af55fa48e6f6fac 100644
--- a/duniterpy/key/signing_key.py
+++ b/duniterpy/key/signing_key.py
@@ -18,7 +18,7 @@ import os
 import re
 from hashlib import scrypt, sha256
 from pathlib import Path
-from typing import Optional, Type, TypeVar, Union
+from typing import Optional, TypeVar, Union
 
 import libnacl.sign
 import pyaes
@@ -57,7 +57,7 @@ class SigningKey(libnacl.sign.Signer):
 
     @classmethod
     def from_credentials(
-        cls: Type[SigningKeyType],
+        cls: type[SigningKeyType],
         salt: Union[str, bytes],
         password: Union[str, bytes],
         scrypt_params: Optional[ScryptParams] = None,
@@ -87,7 +87,7 @@ class SigningKey(libnacl.sign.Signer):
 
     @classmethod
     def from_credentials_file(
-        cls: Type[SigningKeyType],
+        cls: type[SigningKeyType],
         path: Union[Path, str],
         scrypt_params: Optional[ScryptParams] = None,
     ) -> SigningKeyType:
@@ -120,7 +120,7 @@ class SigningKey(libnacl.sign.Signer):
             fh.write(seedhex)
 
     @staticmethod
-    def from_seedhex_file(path: Union[Path, str]) -> Type[SigningKeyType]:
+    def from_seedhex_file(path: Union[Path, str]) -> type[SigningKeyType]:
         """
         Return SigningKey instance from Seedhex file
 
@@ -131,7 +131,7 @@ class SigningKey(libnacl.sign.Signer):
         return SigningKey.from_seedhex(seedhex)
 
     @classmethod
-    def from_seedhex(cls: Type[SigningKeyType], seedhex: str) -> SigningKeyType:
+    def from_seedhex(cls: type[SigningKeyType], seedhex: str) -> SigningKeyType:
         """
         Return SigningKey instance from Seedhex
 
@@ -154,7 +154,7 @@ class SigningKey(libnacl.sign.Signer):
         self.save(path)
 
     @staticmethod
-    def from_private_key(path: Union[Path, str]) -> Type[SigningKeyType]:
+    def from_private_key(path: Union[Path, str]) -> type[SigningKeyType]:
         """
         Read authentication file
         Add public key attribute
@@ -181,7 +181,7 @@ class SigningKey(libnacl.sign.Signer):
 
     @classmethod
     def from_pubsec_file(
-        cls: Type[SigningKeyType], path: Union[Path, str]
+        cls: type[SigningKeyType], path: Union[Path, str]
     ) -> SigningKeyType:
         """
         Return SigningKey instance from Duniter WIF file
@@ -240,7 +240,7 @@ sec: {base58_signing_key}"
     @staticmethod
     def from_wif_or_ewif_file(
         path: Union[Path, str], password: Optional[str] = None
-    ) -> Type[SigningKeyType]:
+    ) -> type[SigningKeyType]:
         """
         Return SigningKey instance from Duniter WIF or EWIF file
 
@@ -263,7 +263,7 @@ sec: {base58_signing_key}"
     @staticmethod
     def from_wif_or_ewif_hex(
         wif_hex: str, password: Optional[str] = None
-    ) -> Type[SigningKeyType]:
+    ) -> type[SigningKeyType]:
         """
         Return SigningKey instance from Duniter WIF or EWIF in hexadecimal format
 
@@ -286,7 +286,7 @@ sec: {base58_signing_key}"
         return result
 
     @staticmethod
-    def from_wif_file(path: Union[Path, str]) -> Type[SigningKeyType]:
+    def from_wif_file(path: Union[Path, str]) -> type[SigningKeyType]:
         """
         Return SigningKey instance from Duniter WIF file
 
@@ -306,7 +306,7 @@ sec: {base58_signing_key}"
         return SigningKey.from_wif_hex(wif_hex)
 
     @classmethod
-    def from_wif_hex(cls: Type[SigningKeyType], wif_hex: str) -> SigningKeyType:
+    def from_wif_hex(cls: type[SigningKeyType], wif_hex: str) -> SigningKeyType:
         """
         Return SigningKey instance from Duniter WIF in hexadecimal format
 
@@ -360,7 +360,7 @@ Data: {wif_key}"
             fh.write(content)
 
     @staticmethod
-    def from_ewif_file(path: Union[Path, str], password: str) -> Type[SigningKeyType]:
+    def from_ewif_file(path: Union[Path, str], password: str) -> type[SigningKeyType]:
         """
         Return SigningKey instance from Duniter EWIF file
 
@@ -382,7 +382,7 @@ Data: {wif_key}"
 
     @classmethod
     def from_ewif_hex(
-        cls: Type[SigningKeyType], ewif_hex: str, password: str
+        cls: type[SigningKeyType], ewif_hex: str, password: str
     ) -> SigningKeyType:
         """
         Return SigningKey instance from Duniter EWIF in hexadecimal format
@@ -490,7 +490,7 @@ Data: {ewif_key}"
             fh.write(content)
 
     @classmethod
-    def from_ssb_file(cls: Type[SigningKeyType], path: str) -> SigningKeyType:
+    def from_ssb_file(cls: type[SigningKeyType], path: str) -> SigningKeyType:
         """
         Return SigningKey instance from ScuttleButt .ssb/secret file
 
@@ -518,7 +518,7 @@ Data: {ewif_key}"
 
     @classmethod
     def from_dubp_mnemonic(
-        cls: Type[SigningKeyType],
+        cls: type[SigningKeyType],
         mnemonic: str,
         scrypt_params: Optional[ScryptParams] = None,
     ) -> SigningKeyType:
diff --git a/examples/send_transaction.py b/examples/send_transaction.py
index cb45153748065b15a68787d70920d034fbd30b6f..8bd005b90b247394da51c7144d12b8113545d7b2 100644
--- a/examples/send_transaction.py
+++ b/examples/send_transaction.py
@@ -15,7 +15,7 @@
 
 import getpass
 import urllib
-from typing import List, Union
+from typing import Union
 
 from duniterpy.api import bma
 from duniterpy.api.client import Client
@@ -43,7 +43,7 @@ def get_transaction_document(
     source: dict,
     from_pubkey: str,
     to_pubkey: str,
-    signing_keys: Union[SigningKey, List[SigningKey]],
+    signing_keys: Union[SigningKey, list[SigningKey]],
 ) -> Transaction:
     """
     Return a Transaction document
diff --git a/pyproject.toml b/pyproject.toml
index ad1d933a7053d577d6deb432d75fb9bcad365787..b967370054befab1853a7be1d54f46239e933577 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,7 +25,7 @@ requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
 
 [tool.poetry.dependencies]
-python = "^3.8.0"
+python = "^3.9.0"
 graphql-core = "^3.2.3"
 websocket-client = "^1.5.2"
 jsonschema = "^4.17.3"