Repository: ethereum/eth-account

Issue:
Allow calling `sign_typed_data` from a `LocalAccount`
### What happened?

While instantiated `LocalAccount`s can sign messages and transactions, they cannot yet sign typed data. This is [only available](https://eth-account.readthedocs.io/en/stable/eth_account.html#eth_account.account.Account.sign_typed_data) through the base `Account` class, having to pass in the key.

It would be helpful to call this through the context of an existing account, so that one could simply do:
```py
my_account.sign_typed_data(full_message=payload)
```

### Code that produced the error

_No response_

### Full error output

```shell
AttributeError: 'LocalAccount' object has no attribute 'signed_typed_data'
```


### Fill this section in if you know how this could or should be fixed

_No response_

### eth-account Version

0.13.4

### Python Version

_No response_

### Operating System

_No response_

### Output from `pip freeze`

_No response_

Discussion:

Solution patch:
diff --git a/eth_account/account_local_actions.py b/eth_account/account_local_actions.py
index 016f80c4..16a748f1 100644
--- a/eth_account/account_local_actions.py
+++ b/eth_account/account_local_actions.py
@@ -72,3 +72,15 @@ def sign_transaction(
         blobs: Optional[Blobs] = None,
     ) -> SignedTransaction:
         pass
+
+    @combomethod
+    @abstractmethod
+    def sign_typed_data(
+        self,
+        private_key: PrivateKeyType,
+        domain_data: Optional[Dict[str, Any]] = None,
+        message_types: Optional[Dict[str, Any]] = None,
+        message_data: Optional[Dict[str, Any]] = None,
+        full_message: Optional[Dict[str, Any]] = None,
+    ) -> SignedMessage:
+        pass
diff --git a/eth_account/signers/local.py b/eth_account/signers/local.py
index 32ea8905..4a9428be 100644
--- a/eth_account/signers/local.py
+++ b/eth_account/signers/local.py
@@ -73,6 +73,9 @@ def __init__(self, key: PrivateKey, account: AccountLocalActions):
 
         self._key_obj: PrivateKey = key
 
+    def __bytes__(self) -> bytes:
+        return self.key
+
     @property
     def address(self) -> ChecksumAddress:
         return self._address
@@ -131,5 +134,27 @@ def sign_transaction(
             self._publicapi.sign_transaction(transaction_dict, self.key, blobs=blobs),
         )
 
-    def __bytes__(self) -> bytes:
-        return self.key
+    def sign_typed_data(
+        self,
+        domain_data: Optional[Dict[str, Any]] = None,
+        message_types: Optional[Dict[str, Any]] = None,
+        message_data: Optional[Dict[str, Any]] = None,
+        full_message: Optional[Dict[str, Any]] = None,
+    ) -> SignedMessage:
+        """
+        Sign the provided EIP-712 message with the local private key.
+
+        This uses the same structure as in
+        :meth:`~eth_account.account.Account.sign_typed_data`, but without a
+        private key argument.
+        """
+        return cast(
+            SignedMessage,
+            self._publicapi.sign_typed_data(
+                private_key=self.key,
+                domain_data=domain_data,
+                message_types=message_types,
+                message_data=message_data,
+                full_message=full_message,
+            ),
+        )
diff --git a/newsfragments/304.feature.rst b/newsfragments/304.feature.rst
new file mode 100644
index 00000000..828794ad
--- /dev/null
+++ b/newsfragments/304.feature.rst
@@ -0,0 +1 @@
+Make ``sign_typed_data`` available in the ``LocalAccount`` class.


Test patch:
diff --git a/tests/core/test_signers.py b/tests/core/test_signers.py
new file mode 100644
index 00000000..7f7863ba
--- /dev/null
+++ b/tests/core/test_signers.py
@@ -0,0 +1,14 @@
+from eth_account import (
+    Account,
+)
+from tests.eip712_messages import (
+    ALL_VALID_EIP712_MESSAGES,
+)
+
+
+def test_sign_typed_data_signing_same_for_account_and_local_account():
+    new_local_account = Account.create()
+    for message in ALL_VALID_EIP712_MESSAGES.values():
+        assert Account.sign_typed_data(
+            new_local_account.key, full_message=message
+        ) == new_local_account.sign_typed_data(full_message=message)