使用web3py调用ropsten.infura.io触发405错误的问题求助
问题原因与解决方案
你遇到的405错误本质是Infura不支持personal命名空间的RPC方法,也不允许在其托管节点上解锁账户或代为签名交易。Infura作为公共节点服务,不会存储用户私钥,也禁用了这类需要访问用户私钥的操作,所以直接调用w3.personal.unlockAccount或让节点自动签名交易都会被拒绝。
为什么会报错?
w3.personal.unlockAccount属于personal系列RPC方法,Infura明确禁用了这类涉及私钥操作的接口,因此请求返回405 Method Not Allowed。- 当你调用
contract_instance.giveToken(..., transact={'from': account_a})时,web3py默认会尝试让连接的Infura节点用本地存储的账户私钥签名交易,但Infura节点上没有你的私钥,也不提供签名服务,所以同样触发405错误。
正确的处理方式:本地签名交易
你需要在本地用自己的私钥完成交易签名,再将签名后的交易发送到Infura。具体步骤和修改后的代码如下:
1. 安全管理私钥
绝对不要把私钥硬编码在代码里,建议通过环境变量、加密配置文件等方式加载。下面示例中用环境变量来获取私钥和Infura密钥。
2. 修改后的web3py代码
import json import os from eth_utils import to_checksum_address from web3 import Web3, HTTPProvider from web3.contract import ConciseContract from web3.middleware import geth_poa_middleware # 从环境变量加载敏感信息(安全做法) INFURA_API_KEY = os.getenv("INFURA_API_KEY") ACCOUNT_PRIVATE_KEY = os.getenv("ACCOUNT_PRIVATE_KEY") w3 = Web3(HTTPProvider(f'https://ropsten.infura.io/v3/{INFURA_API_KEY}')) w3.middleware_stack.inject(geth_poa_middleware, layer=0) # 合约地址与ABI(ABI可从Remix或Etherscan获取) contract_address = to_checksum_address('0xFb294910d8193DeB9a294B51F22D8878ad15f2E8') contract_abi = [ {"constant":false,"inputs":[{"name":"dest","type":"address"},{"name":"amount","type":"uint256"}],"name":"giveToken","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}, {"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, {"constant":true,"inputs":[],"name":"myBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, {"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}, {"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"receiver","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"increaseToken","type":"event"} ] # 实例化合约 contract_instance = w3.eth.contract(abi=contract_abi, address=contract_address, ContractFactoryClass=ConciseContract) account_a = to_checksum_address("0xCa2d22Cb8ff54f2D1DCfDBb75DD6411a5A0ee6f1") account_b = to_checksum_address("0x8F8d1bc97E8939e3932BfBeb923A1B2B972D5A9A") # 打印交易前余额 print("Contract: {}".format(contract_address)) print("Before give token") print('My Balance: {}'.format(contract_instance.myBalance())) print('{} Balance: {}'.format(account_a, contract_instance.getBalance(account_a))) print('{} Balance: {}'.format(account_b, contract_instance.getBalance(account_b))) token_amount = 1000 # 1. 构建交易对象 nonce = w3.eth.getTransactionCount(account_a) # 获取账户当前nonce,避免交易重复 gas_price = w3.eth.gasPrice # 使用当前网络默认gas价格 gas_limit = contract_instance.giveToken.estimateGas(account_b, token_amount) # 估算交易所需gas tx_data = contract_instance.giveToken.encodeABI(args=[account_b, token_amount]) # 编码合约调用数据 tx = { 'from': account_a, 'to': contract_address, 'value': 0, 'gas': gas_limit, 'gasPrice': gas_price, 'nonce': nonce, 'data': tx_data, 'chainId': 3 # Ropsten测试网的chainId为3 } # 2. 用私钥签名交易 signed_tx = w3.eth.account.sign_transaction(tx, private_key=ACCOUNT_PRIVATE_KEY) # 3. 发送签名后的交易 tx_hash = w3.eth.sendRawTransaction(signed_tx.rawTransaction) print(f"交易已发送,哈希: {w3.toHex(tx_hash)}") # 等待交易上链确认 tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) print(f"交易已确认,区块号: {tx_receipt.blockNumber}") # 验证交易结果 try: assert contract_instance.getBalance(account_b) == token_amount except AssertionError as e: print("Test Error: {}".format(e)) print("Amount: {} Expected: {}".format(contract_instance.getBalance(account_b), token_amount)) else: print("After give token") print('My Balance: {}'.format(contract_instance.myBalance())) print('{} Balance: {}'.format(account_a, contract_instance.getBalance(account_a))) print('{} Balance: {}'.format(account_b, contract_instance.getBalance(account_b))) finally: print('Done')
关键细节说明
- 私钥安全:用环境变量加载私钥,避免泄露风险;也可以用加密钱包(如web3py支持的keystore文件)来管理私钥。
- Nonce:确保每笔交易的nonce唯一,防止交易重复或失败。
- Gas设置:
estimateGas能自动估算合约调用所需gas,也可手动设置一个合理值;gasPrice可根据网络拥堵情况调整。 - ChainId:指定正确的链ID,防止交易被发送到其他网络。
额外提示
你在Remix中能正常交易,是因为Remix会通过浏览器钱包(如MetaMask)在本地完成签名,再将交易发送到网络——这和我们上面的本地签名逻辑完全一致。
内容的提问来源于stack exchange,提问作者Henry Kim
相关产品推荐
相关产品推荐

