Repository: ethereum/web3.py

Issue:
Add sign_typed_data method to AsyncEth class
### What was wrong?

Some methods are still unavailable on the AsyncEth class.

Related to Issue # missing sign_typed_data

### How was it fixed?

Added class, tests & updated docs (split into 2 commits)

### Todo:
- [ ] Add entry to the [release notes](https://github.com/ethereum/web3.py/blob/master/newsfragments/README.md)

#### Cute Animal Picture

![Put a link to a cute animal picture inside the parenthesis-->](https://image.spreadshirtmedia.com/image-server/v1/mp/products/T1459A839PA3861PT28D1044224305W10000H8111/views/1,width=800,height=800,appearanceId=839,backgroundColor=F2F2F2/narwhal-cute-narwhal-cute-sticker.jpg)



Discussion:
What do I add to the release notes here? https://github.com/ethereum/web3.py/blob/master/newsfragments/README.md

Resolving patch:
diff --git a/web3/eth/async_eth.py b/web3/eth/async_eth.py
--- a/web3/eth/async_eth.py
+++ b/web3/eth/async_eth.py
@@ -558,6 +558,20 @@ async def sign(
     async def sign_transaction(self, transaction: TxParams) -> SignedTx:
         return await self._sign_transaction(transaction)
 
+    # eth_signTypedData
+
+    _sign_typed_data: Method[
+        Callable[[Union[Address, ChecksumAddress, ENS], str], Awaitable[HexStr]]
+    ] = Method(
+        RPC.eth_signTypedData,
+        mungers=[default_root_munger],
+    )
+
+    async def sign_typed_data(
+        self, account: Union[Address, ChecksumAddress, ENS], data: str
+    ) -> HexStr:
+        return await self._sign_typed_data(account, data)
+
     # eth_getUncleCountByBlockHash
     # eth_getUncleCountByBlockNumber
 


Test patch:
diff --git a/tests/integration/go_ethereum/common.py b/tests/integration/go_ethereum/common.py
--- a/tests/integration/go_ethereum/common.py
+++ b/tests/integration/go_ethereum/common.py
@@ -97,4 +97,18 @@ class GoEthereumPersonalModuleTest(GoEthereumPersonalModuleTest):
 
 
 class GoEthereumAsyncEthModuleTest(AsyncEthModuleTest):
-    pass
+    @pytest.mark.xfail(reason="eth_signTypedData has not been released in geth")
+    async def test_eth_sign_typed_data(
+        self, async_w3, unlocked_account_dual_type, async_skip_if_testrpc
+    ):
+        await super().test_eth_sign_typed_data(
+            async_w3, unlocked_account_dual_type, async_skip_if_testrpc
+        )
+
+    @pytest.mark.xfail(reason="eth_signTypedData has not been released in geth")
+    async def test_invalid_eth_sign_typed_data(
+        self, async_w3, unlocked_account_dual_type, async_skip_if_testrpc
+    ):
+        await super().test_invalid_eth_sign_typed_data(
+            async_w3, unlocked_account_dual_type, async_skip_if_testrpc
+        )
diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py
--- a/web3/_utils/module_testing/eth_module.py
+++ b/web3/_utils/module_testing/eth_module.py
@@ -168,6 +168,115 @@ async def test_async_eth_sign_transaction(
         )
         assert result["tx"]["nonce"] == txn_params["nonce"]
 
+    @pytest.mark.asyncio
+    async def test_eth_sign_typed_data(
+        self,
+        async_w3: "AsyncWeb3",
+        unlocked_account_dual_type: ChecksumAddress,
+        async_skip_if_testrpc: Callable[["AsyncWeb3"], None],
+    ) -> None:
+        validJSONMessage = """
+            {
+                "types": {
+                    "EIP712Domain": [
+                        {"name": "name", "type": "string"},
+                        {"name": "version", "type": "string"},
+                        {"name": "chainId", "type": "uint256"},
+                        {"name": "verifyingContract", "type": "address"}
+                    ],
+                    "Person": [
+                        {"name": "name", "type": "string"},
+                        {"name": "wallet", "type": "address"}
+                    ],
+                    "Mail": [
+                        {"name": "from", "type": "Person"},
+                        {"name": "to", "type": "Person"},
+                        {"name": "contents", "type": "string"}
+                    ]
+                },
+                "primaryType": "Mail",
+                "domain": {
+                    "name": "Ether Mail",
+                    "version": "1",
+                    "chainId": "0x01",
+                    "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
+                },
+                "message": {
+                    "from": {
+                        "name": "Cow",
+                        "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
+                    },
+                    "to": {
+                        "name": "Bob",
+                        "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
+                    },
+                    "contents": "Hello, Bob!"
+                }
+            }
+        """
+        async_skip_if_testrpc(async_w3)
+        signature = HexBytes(
+            await async_w3.eth.sign_typed_data(
+                unlocked_account_dual_type, json.loads(validJSONMessage)
+            )
+        )
+        assert len(signature) == 32 + 32 + 1
+
+    @pytest.mark.asyncio
+    async def test_invalid_eth_sign_typed_data(
+        self,
+        async_w3: "AsyncWeb3",
+        unlocked_account_dual_type: ChecksumAddress,
+        async_skip_if_testrpc: Callable[["AsyncWeb3"], None],
+    ) -> None:
+        async_skip_if_testrpc(async_w3)
+        invalid_typed_message = """
+            {
+                "types": {
+                    "EIP712Domain": [
+                        {"name": "name", "type": "string"},
+                        {"name": "version", "type": "string"},
+                        {"name": "chainId", "type": "uint256"},
+                        {"name": "verifyingContract", "type": "address"}
+                    ],
+                    "Person": [
+                        {"name": "name", "type": "string"},
+                        {"name": "wallet", "type": "address"}
+                    ],
+                    "Mail": [
+                        {"name": "from", "type": "Person"},
+                        {"name": "to", "type": "Person[2]"},
+                        {"name": "contents", "type": "string"}
+                    ]
+                },
+                "primaryType": "Mail",
+                "domain": {
+                    "name": "Ether Mail",
+                    "version": "1",
+                    "chainId": "0x01",
+                    "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
+                },
+                "message": {
+                    "from": {
+                        "name": "Cow",
+                        "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
+                    },
+                    "to": [{
+                        "name": "Bob",
+                        "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
+                    }],
+                    "contents": "Hello, Bob!"
+                }
+            }
+        """
+        with pytest.raises(
+            ValueError,
+            match=r".*Expected 2 items for array type Person\[2\], got 1 items.*",
+        ):
+            await async_w3.eth.sign_typed_data(
+                unlocked_account_dual_type, json.loads(invalid_typed_message)
+            )
+
     @pytest.mark.asyncio
     async def test_async_eth_sign_transaction_legacy(
         self, async_w3: "AsyncWeb3", unlocked_account: ChecksumAddress