You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用web3.py在Base Layer 2执行代币购买交易时遭遇Execution Reverted问题

使用web3.py在Base Layer 2执行代币购买交易时遭遇Execution Reverted问题

我正在尝试用Python和web3.py写一个服务,在Base链的某个合约上执行“购买”代币的交易。奇怪的是,UI上执行相同的购买操作完全没问题,但我代码里发的交易虽然能拿到哈希,却总是被回滚。我用的测试钱包已经有几笔失败的交易记录了。

我的代码如下(交易发送成功但被回滚,我试过把gas值转成十六进制(和网站上的交易格式一致),也试过移除value字段,但都没用):

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Holding
import os
import sys
from web3 import Web3
from eth_account import Account
from .config import w3, CONTRACT_ADDRESS, GAS_LIMIT, VIRTUALS_CA
from .contract_abi import CONTRACT_ABI
from .virtual_protocol_contract import VIRTUAL_ABI
from .utils import estimate_gas_price

class BuyTokenView(APIView):
    def post(self, request):
        data = request.data

        # Validate input
        if 'contract_address' not in data:
            return Response({"error": "Missing required fields"}, status=status.HTTP_400_BAD_REQUEST)

        ca = data['contract_address']
        amount = data['amount']
        private_key = load_environment()
        account = setup_account(private_key)
        print(f"Connected with address: {account.address}")
        # Execute transaction
        execute_buy_transaction(account, ca, float(amount))

        return Response({"Message": f"Buying Token {ca}"}, status=status.HTTP_200_OK)


def load_environment():
    """Load environment variables."""
    private_key = os.getenv('PRIVATE_KEY')
    if not private_key:
        print("Error: PRIVATE_KEY not found in environment variables")
        sys.exit(1)
    return private_key


def setup_account(private_key):
    """Setup account from private key."""
    try:
        account = Account.from_key(private_key)
        return account
    except ValueError as e:
        print(f"Error setting up account: {e}")
        sys.exit(1)


def execute_buy_transaction(account, token_address, amount):
    """Execute token purchase transaction."""
    contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=CONTRACT_ABI)
    virtuals_contract = w3.eth.contract(address=VIRTUALS_CA, abi=VIRTUAL_ABI)
    float_value = float(amount)
    approve_tokens(account, virtuals_contract, int(amount))

    # Prepare transaction
    transaction = contract.functions.buy(amountIn=int(amount), tokenAddress=token_address).build_transaction({
        'from': account.address,
        'gas': hex(GAS_LIMIT),
        'maxFeePerGas': hex(estimate_gas_price(w3)),
        'maxPriorityFeePerGas': hex(w3.to_wei('2', 'gwei')),
        'nonce': hex(w3.eth.get_transaction_count(account.address)),
    })
    transaction.pop('value', None)

    # Sign and send transaction
    try:
        signed_txn = w3.eth.account.sign_transaction(transaction, account.key)
        tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
        print(f"Transaction sent. Hash: {tx_hash.hex()}")

        # Wait for transaction receipt
        print("Waiting for transaction confirmation...")
        receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        return receipt
    except Exception as e:
        print(f"Error executing transaction: {e}")
        return None


# Function to approve tokens
def approve_tokens(account, contract, amount):
    nonce = w3.eth.get_transaction_count(account.address)

    tx = {
        'nonce': nonce,
        'to': contract.address,
        'value': w3.to_wei(0, 'ether'),
        'gas': 200000,
        'gasPrice': w3.eth.gas_price,
        'data': contract.encode_abi("approve", args=[account.address, amount]),
        'chainId': w3.eth.chain_id
    }
    try:
        signed_tx = w3.eth.account.sign_transaction(tx, account.key)
        tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
        print(f"Approve Transaction sent. Hash: {tx_hash.hex()}")

        # Wait for transaction receipt
        print("Waiting for transaction confirmation...")
        receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
        return receipt
    except Exception as e:
        print(f"Error approving tokens: {e}")
        return None

可能的问题排查方向及解决方案

作为踩过不少web3交易回滚坑的开发者,给你几个优先排查的点:

  1. 授权对象完全错了!
    看你的approve_tokens函数,你把代币授权给了自己的钱包地址account.address,但实际购买逻辑里,是需要把代币授权给负责执行购买的合约(也就是CONTRACT_ADDRESS)啊!UI上的交易肯定是授权给了这个合约地址,你这里参数写反了,改成args=[CONTRACT_ADDRESS, amount]试试——这大概率是最核心的问题,合约没权限动你的代币,直接就回滚了。

  2. 金额单位是否匹配?
    你传入buy函数的amountIn是int(amount),要确认这个数值的精度是不是合约要求的。比如如果是USDC这类6位小数的代币,你直接传用户输入的100,就会变成100 wei(也就是0.0000001 USDC),这点钱肯定买不到东西,合约直接因余额不足回滚。要根据代币的小数位数转换,比如USDC就用int(amount * 10**6),或者用w3.to_wei(amount, 'ether')对应18位小数的代币。

  3. Gas参数别瞎转十六进制
    web3.py的build_transaction接受整数类型的gas参数,你转成hex反而可能出问题。把hex(GAS_LIMIT)、hex(estimate_gas_price(w3))这些都改成直接传整数,比如:

    transaction = contract.functions.buy(amountIn=int(amount), tokenAddress=token_address).build_transaction({
        'from': account.address,
        'gas': GAS_LIMIT,
        'maxFeePerGas': estimate_gas_price(w3),
        'maxPriorityFeePerGas': w3.to_wei('2', 'gwei'),
        'nonce': w3.eth.get_transaction_count(account.address),
        'chainId': w3.eth.chain_id
    })
    

    顺便加上chainId字段,避免链ID不匹配的隐性问题。

  4. 提前模拟交易排查错误
    别等交易发出去回滚才看问题,用call()方法模拟交易,直接在本地拿到错误信息:

    try:
        # 模拟购买交易,提前发现问题
        contract.functions.buy(amountIn=int(amount), tokenAddress=token_address).call({
            'from': account.address,
        })
    except Exception as e:
        print(f"模拟交易失败,具体错误:{e}")
    

    比如如果是授权不足,这里会直接抛出Insufficient allowance的错误,比查区块浏览器还快。

总结

先改授权的spender地址,这是最可能的元凶,然后检查金额单位,最后用模拟交易提前排错,应该能解决你的问题。

备注:内容来源于stack exchange,提问作者degenTy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 13:07:58