Repository: ethereum/web3.py

Issue:
Add async eth.sign_transaction
### What was wrong?

Some methods are still unavailable on the `AsyncEth` class.

### How can it be fixed?

Following the pattern already established, add `sign_transaction` to the `AsyncEth` class, and add corresponding tests. The `AsyncHTTPProvider` docs in `docs/provider.rst` will also need to be updated.



Discussion:
@kclowes I am interested in contributing. Can you guide me by providing some references?

Sure, thanks! #2833 is a good example. Basically, copy/paste the existing sync `send_transaction` tests and add `async`/`await` where needed, and then add the method in the `AsyncEth` class. Let me know if you have other questions! Or feel free to put up a PR and one of us will help review. 

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
@@ -67,6 +67,7 @@
     FilterParams,
     LogReceipt,
     Nonce,
+    SignedTx,
     SyncStatus,
     TxData,
     TxParams,
@@ -547,6 +548,16 @@ async def sign(
     ) -> HexStr:
         return await self._sign(account, data, hexstr, text)
 
+    # eth_signTransaction
+
+    _sign_transaction: Method[Callable[[TxParams], Awaitable[SignedTx]]] = Method(
+        RPC.eth_signTransaction,
+        mungers=[default_root_munger],
+    )
+
+    async def sign_transaction(self, transaction: TxParams) -> SignedTx:
+        return await self._sign_transaction(transaction)
+
     # eth_newFilter, eth_newBlockFilter, eth_newPendingTransactionFilter
 
     filter: Method[


Test patch:
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
@@ -142,6 +142,107 @@ async def test_eth_send_transaction_legacy(
         assert txn["gas"] == 21000
         assert txn["gasPrice"] == txn_params["gasPrice"]
 
+    @pytest.mark.asyncio
+    async def test_async_eth_sign_transaction(
+        self, async_w3: "AsyncWeb3", unlocked_account: ChecksumAddress
+    ) -> None:
+        txn_params: TxParams = {
+            "from": unlocked_account,
+            "to": unlocked_account,
+            "value": Wei(1),
+            "gas": 21000,
+            "maxFeePerGas": async_w3.to_wei(2, "gwei"),
+            "maxPriorityFeePerGas": async_w3.to_wei(1, "gwei"),
+            "nonce": Nonce(0),
+        }
+        result = await async_w3.eth.sign_transaction(txn_params)
+        signatory_account = async_w3.eth.account.recover_transaction(result["raw"])
+        assert unlocked_account == signatory_account
+        assert result["tx"]["to"] == txn_params["to"]
+        assert result["tx"]["value"] == txn_params["value"]
+        assert result["tx"]["gas"] == txn_params["gas"]
+        assert result["tx"]["maxFeePerGas"] == txn_params["maxFeePerGas"]
+        assert (
+            result["tx"]["maxPriorityFeePerGas"] == txn_params["maxPriorityFeePerGas"]
+        )
+        assert result["tx"]["nonce"] == txn_params["nonce"]
+
+    @pytest.mark.asyncio
+    async def test_async_eth_sign_transaction_legacy(
+        self, async_w3: "AsyncWeb3", unlocked_account: ChecksumAddress
+    ) -> None:
+        txn_params: TxParams = {
+            "from": unlocked_account,
+            "to": unlocked_account,
+            "value": Wei(1),
+            "gas": 21000,
+            "gasPrice": await async_w3.eth.gas_price,
+            "nonce": Nonce(0),
+        }
+        result = await async_w3.eth.sign_transaction(txn_params)
+        signatory_account = async_w3.eth.account.recover_transaction(result["raw"])
+        assert unlocked_account == signatory_account
+        assert result["tx"]["to"] == txn_params["to"]
+        assert result["tx"]["value"] == txn_params["value"]
+        assert result["tx"]["gas"] == txn_params["gas"]
+        assert result["tx"]["gasPrice"] == txn_params["gasPrice"]
+        assert result["tx"]["nonce"] == txn_params["nonce"]
+
+    @pytest.mark.asyncio
+    async def test_async_eth_sign_transaction_hex_fees(
+        self, async_w3: "AsyncWeb3", unlocked_account: ChecksumAddress
+    ) -> None:
+        txn_params: TxParams = {
+            "from": unlocked_account,
+            "to": unlocked_account,
+            "value": Wei(1),
+            "gas": 21000,
+            "maxFeePerGas": hex(async_w3.to_wei(2, "gwei")),
+            "maxPriorityFeePerGas": hex(async_w3.to_wei(1, "gwei")),
+            "nonce": Nonce(0),
+        }
+        result = await async_w3.eth.sign_transaction(txn_params)
+        signatory_account = async_w3.eth.account.recover_transaction(result["raw"])
+        assert unlocked_account == signatory_account
+        assert result["tx"]["to"] == txn_params["to"]
+        assert result["tx"]["value"] == txn_params["value"]
+        assert result["tx"]["gas"] == txn_params["gas"]
+        assert result["tx"]["maxFeePerGas"] == int(str(txn_params["maxFeePerGas"]), 16)
+        assert result["tx"]["maxPriorityFeePerGas"] == int(
+            str(txn_params["maxPriorityFeePerGas"]), 16
+        )
+        assert result["tx"]["nonce"] == txn_params["nonce"]
+
+    @pytest.mark.asyncio
+    @pytest.mark.xfail(
+        reason="async name_to_address_middleware has not been implemented yet"
+    )
+    async def test_async_eth_sign_transaction_ens_names(
+        self, async_w3: "AsyncWeb3", unlocked_account: ChecksumAddress
+    ) -> None:
+        with ens_addresses(async_w3, {"unlocked-account.eth": unlocked_account}):
+            txn_params: TxParams = {
+                "from": "unlocked-account.eth",
+                "to": "unlocked-account.eth",
+                "value": Wei(1),
+                "gas": 21000,
+                "maxFeePerGas": async_w3.to_wei(2, "gwei"),
+                "maxPriorityFeePerGas": async_w3.to_wei(1, "gwei"),
+                "nonce": Nonce(0),
+            }
+            result = await async_w3.eth.sign_transaction(txn_params)
+            signatory_account = async_w3.eth.account.recover_transaction(result["raw"])
+            assert unlocked_account == signatory_account
+            assert result["tx"]["to"] == unlocked_account
+            assert result["tx"]["value"] == txn_params["value"]
+            assert result["tx"]["gas"] == txn_params["gas"]
+            assert result["tx"]["maxFeePerGas"] == txn_params["maxFeePerGas"]
+            assert (
+                result["tx"]["maxPriorityFeePerGas"]
+                == txn_params["maxPriorityFeePerGas"]
+            )
+            assert result["tx"]["nonce"] == txn_params["nonce"]
+
     @pytest.mark.asyncio
     async def test_eth_send_transaction(
         self, async_w3: "AsyncWeb3", unlocked_account_dual_type: ChecksumAddress