ERC-4337 Paymaster 完整實作指南:從原理到部署的工程實踐

Paymaster 是 ERC-4337 帳戶抽象架構中至關重要的組件,它解決了以太坊用戶面臨的核心痛點:必須持有 ETH 才能發起交易。本文深入解析 Paymaster 的技術原理、三種主要實現類型(Signature-based、Conditional、Token Paymaster)、完整的智慧合約程式碼範例,以及實際部署的最佳實踐。涵蓋 TPM/SGX 硬體認證、零知識數據可用性證明,以及邊緣計算與區塊鏈二層架構的整合方案。

ERC-4337 Paymaster 完整實作指南:從原理到部署的工程實踐

概述

Paymaster 是 ERC-4337 帳戶抽象架構中至關重要的組件,它解決了以太坊用戶面臨的一個核心痛點:必須持有 ETH 才能發起交易。透過 Paymaster,用戶可以使用 ERC-20 代幣(如 USDC、USDT)支付 Gas 費用,或者由第三方應用程式補貼用戶的 Gas 成本。這種設計大幅降低了區塊鏈應用的進入門檻,為用戶提供了更加流暢的體驗。

截至 2026 年第一季度,Paymaster 已經成為智慧合約錢包生態系統中不可或缺的基础设施。主流錢包如 MetaMask、Coinbase Wallet 都已支持 Paymaster 功能,眾多 DeFi 協議也開始集成 Paymaster 來補貼用戶的 Gas 費用。本文深入解析 Paymaster 的技術原理、三種主要實現類型、完整的智慧合約程式碼範例,以及實際部署的最佳實踐。

一、Paymaster 技術架構解析

1.1 Paymaster 在 ERC-4337 中的角色

在 ERC-4337 的架構中,Paymaster 扮演著「燃費擔保人」的角色。整個交易流程如下:用戶構造 UserOperation 物件,其中包含用戶意圖的完整描述;用戶(或其錢包)對 UserOperation 進行簽名;UserOperation 被發送到 Bundler 網絡;Bundler 將 UserOperation 提交給 EntryPoint 合約;EntryPoint 驗證用戶帳戶的簽名,然後調用 Paymaster 驗證是否可以支付 Gas;Paymaster 根據其邏輯決定是否願意承擔 Gas 費用;若 Paymaster 同意,EntryPoint 執行用戶操作,並從 Paymaster 的存款中扣除 Gas費用。

這種設計的關鍵創新在於將「交易驗證」與「費用支付」分離。傳統 EOA 交易中,這兩者是綁定的——只有持有私鑰的人才能發起交易,也只有 ETH 持有者才能支付費用。Paymaster 的引入使得這兩個職責可以由不同的實體承擔。

1.2 Paymaster 的核心介面

ERC-4337 定義了 Paymaster 的標準介面,任何實現該介面的合約都可以作為 Paymaster 使用:

// ERC-4337 Paymaster 介面
// 參考: https://eips.ethereum.org/EIPS/eip-4337

interface IPaymaster {
    /**
     * 驗證 UserOperation 並返回上下文
     * @param userOp 用戶操作
     * @param requiredPreFund 需要的前期資金
     * @return context 上下文信息,將傳遞給 postOp
     */
    function validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 requestId,
        uint256 requiredPreFund
    ) external returns (bytes memory context);

    /**
     * 在主操作執行後被調用
     * @param mode 操作模式
     * @param context 從 validatePaymasterUserOp 返回的上下文
     * @param actualGasCost 實際消耗的 Gas 費用
     */
    function postOp(
        PostOpMode mode,
        bytes calldata context,
        uint256 actualGasCost
    ) external;
}

enum PostOpMode {
    opMode,        // 主操作成功執行
    revert,        // 主操作回滾
    postOpReverted // postOp 本身回滾
}

1.3 Paymaster 的三種主要類型

根據不同的業務場景和激勵機制,Paymaster 可以分為以下三種類型:

第一類:Signature-based Paymaster(基於簽名的 Paymaster)

這種 Paymaster 由服務商運營,服務商審查用戶的 UserOperation 並決定是否願意支付 Gas。服務商可以是錢包提供商、DeFi 協議、或任何希望補貼用戶費用的實體。用戶需要獲得服務商的簽名授權,證明服務商同意為該操作支付費用。

這種模式的典型應用場景包括:錢包提供商為其用戶支付前幾筆交易的費用,降低新用戶進入門檻;DeFi 協議為使用其服務的用戶補貼 Gas 費用,激勵用戶互動;遊戲開發商為遊戲內交易支付費用,提升用戶體驗。

第二類:Conditional Paymaster(條件式 Paymaster)

這種 Paymaster根據預定義的條件自動決定是否支付費用,而無需服務商簽名。條件可以是:用戶的存款餘額超過閾值;用戶已完成身份驗證(KYC);操作涉及特定的代幣或協議;網路費用在特定範圍內。

條件式 Paymaster 的優勢在於無需服務商持續在線審查,降低了運營成本,適合大規模自動化場景。

第三類:Token Paymaster(代幣 Paymaster)

這種 Paymaster 允許用戶使用 ERC-20 代幣支付 Gas 費用,而不是 ETH。Paymaster 在收到用戶的代幣後,用其持有的 ETH 為用戶支付 Gas 費用,並保留用戶的代幣作為報酬。這種模式允許用戶在不持有 ETH 的情況下使用以太坊網路。

代幣 Paymaster 的核心機制是匯率換算:Paymaster 運營商設定一個代幣與 ETH 的匯率,確保其收到的代幣價值足以覆蓋支付的 Gas 費用。這個匯率通常會包含一定的溢價作為服務費。

二、基於簽名 Paymaster 的完整實作

2.1 合約架構設計

基於簽名 Paymaster 的核心組件包括:Paymaster 合約本身,負責驗證簽名並授權支付;驗Signer 伺服器,負責批准用戶的請求並生成簽名;前端集成,引導用戶獲取簽名並構造 UserOperation。

以下是一個完整的基於簽名 Paymaster 實現:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/IEntryPoint.sol";

/**
 * @title SignaturePaymaster
 * @dev 基於簽名的 Paymaster 實現
 * 
 * 運作流程:
 * 1. 用戶請求服務商批准操作
 * 2. 服務商驗證用戶身份並生成簽名
 * 3. 用戶構造包含簽名的 UserOperation
 * 4. Paymaster 驗證簽名有效性並授權支付
 */
contract SignaturePaymaster is Ownable, ReentrancyGuard {
    using ECDSA for bytes32;

    // EntryPoint 合約地址
    IEntryPoint public immutable entryPoint;

    // 驗證器地址集合(可以有多個驗證節點)
    mapping(address => bool) public verifiers;
    
    // 驗證器集合管理
    address[] public verifierList;
    
    // 運營商地址(可以提取收益)
    address public operator;
    
    // Paymaster 存款
    mapping(address => uint256) public deposits;
    
    // 每個用戶的 Gas 額度(防止濫用)
    mapping(address => uint256) public userGasLimits;
    
    // 默認 Gas 限制
    uint256 public defaultGasLimit = 300000;
    
    // 費用溢價係數(默認 130%,即 1.3 倍)
    uint256 public priceMarkup = 130;
    
    // 事件記錄
    event VerifierAdded(address indexed verifier);
    event VerifierRemoved(address indexed verifier);
    event DepositReceived(address indexed user, uint256 amount);
    event GasSponsored(address indexed user, uint256 gasUsed);
    event OperatorUpdated(address indexed oldOperator, address indexed newOperator);

    modifier onlyVerifier() {
        require(verifiers[msg.sender], "Not authorized verifier");
        _;
    }

    constructor(address _entryPoint, address _owner) {
        require(_entryPoint != address(0), "Invalid EntryPoint");
        entryPoint = IEntryPoint(_entryPoint);
        operator = _owner;
        transferOwnership(_owner);
    }

    /**
     * @dev 驗證 UserOperation 並返回上下文
     */
    function validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 requestId,
        uint256 requiredPreFund
    ) external override returns (bytes memory context) {
        // 只能由 EntryPoint 調用
        require(msg.sender == address(entryPoint), "Not EntryPoint");

        // 解析簽名
        (bytes memory signature, address user) = abi.decode(
            userOp.signature,
            (bytes, address)
        );

        // 驗證簽名
        bytes32 hash = requestId.toEthSignedMessageHash();
        address signer = hash.recover(signature);
        
        require(verifiers[signer], "Invalid verifier signature");
        
        // 檢查用戶 Gas 限制
        uint256 gasLimit = userGasLimits[user];
        if (gasLimit == 0) {
            gasLimit = defaultGasLimit;
        }
        
        // 檢查 Paymaster 存款是否足夠
        uint256 maxGasCost = gasLimit * block.gasprice * priceMarkup / 100;
        require(
            deposits[address(this)] >= maxGasCost,
            "Insufficient deposit"
        );

        // 返回上下文,包含用戶地址和 Gas 限制
        context = abi.encode(user, gasLimit, block.timestamp);

        // 更新用戶 Gas 限額(如果需要)
        if (userGasLimits[user] == 0 && defaultGasLimit > 0) {
            userGasLimits[user] = defaultGasLimit;
        }
    }

    /**
     * @dev 主操作執行後的回調
     */
    function postOp(
        PostOpMode mode,
        bytes calldata context,
        uint256 actualGasCost
    ) external override nonReentrant {
        require(msg.sender == address(entryPoint), "Not EntryPoint");

        (address user, uint256 gasLimit, uint256 timestamp) = abi.decode(
            context,
            (address, uint256, uint256)
        );

        // 記錄實際使用的 Gas 費用
        emit GasSponsored(user, actualGasCost);

        // 從 Paymaster 存款中扣除費用
        // 注意:實際扣除由 EntryPoint 完成,這裡記錄日誌
    }

    /**
     * @dev 添加驗證器
     */
    function addVerifier(address _verifier) external onlyOwner {
        require(_verifier != address(0), "Invalid verifier");
        require(!verifiers[_verifier], "Verifier already added");
        
        verifiers[_verifier] = true;
        verifierList.push(_verifier);
        
        emit VerifierAdded(_verifier);
    }

    /**
     * @dev 移除驗證器
     */
    function removeVerifier(address _verifier) external onlyOwner {
        require(verifiers[_verifier], "Verifier not exists");
        
        verifiers[_verifier] = false;
        
        // 從列表中移除
        for (uint256 i = 0; i < verifierList.length; i++) {
            if (verifierList[i] == _verifier) {
                verifierList[i] = verifierList[verifierList.length - 1];
                verifierList.pop();
                break;
            }
        }
        
        emit VerifierRemoved(_verifier);
    }

    /**
     * @dev 設置用戶的 Gas 限額
     */
    function setUserGasLimit(address _user, uint256 _limit) external onlyOwner {
        userGasLimits[_user] = _limit;
    }

    /**
     * @dev 批量設置用戶 Gas 限額
     */
    function setUserGasLimits(address[] calldata _users, uint256[] calldata _limits) 
        external 
        onlyOwner 
    {
        require(_users.length == _limits.length, "Length mismatch");
        
        for (uint256 i = 0; i < _users.length; i++) {
            userGasLimits[_users[i]] = _limits[i];
        }
    }

    /**
     * @dev 設置費用溢價
     */
    function setPriceMarkup(uint256 _markup) external onlyOwner {
        require(_markup >= 100 && _markup <= 200, "Invalid markup");
        priceMarkup = _markup;
    }

    /**
     * @dev 設置運營商
     */
    function setOperator(address _operator) external onlyOwner {
        require(_operator != address(0), "Invalid operator");
        address oldOperator = operator;
        operator = _operator;
        emit OperatorUpdated(oldOperator, _operator);
    }

    /**
     * @dev 存款到 Paymaster
     */
    function deposit() external payable {
        deposits[address(this)] += msg.value;
        deposits[msg.sender] += msg.value;
        emit DepositReceived(msg.sender, msg.value);
    }

    /**
     * @dev 從 Paymaster 存款中提取
     */
    function withdrawTo(address payable _to, uint256 _amount) external onlyOwner {
        require(_to != address(0), "Invalid address");
        require(_amount <= deposits[address(this)], "Insufficient balance");
        
        deposits[address(this)] -= _amount;
        (bool success,) = _to.call{value: _amount}("");
        require(success, "Transfer failed");
    }

    /**
     * @dev 獲取 Paymaster 存款餘額
     */
    function getDeposit() external view returns (uint256) {
        return deposits[address(this)];
    }

    /**
     * @dev 獲取驗證器列表
     */
    function getVerifiers() external view returns (address[] memory) {
        return verifierList;
    }

    /**
     * @dev 估算交易費用
     */
    function estimateGas(
        UserOperation calldata userOp,
        uint256 _gasPrice
    ) external view returns (uint256) {
        uint256 gasUsed = defaultGasLimit;
        uint256 maxCost = gasUsed * _gasPrice * priceMarkup / 100;
        return maxCost;
    }

    // 接收 ETH
    receive() external payable {
        deposits[address(this)] += msg.value;
    }
}

2.2 驗證器伺服器實現

Paymaster 需要一個離鏈的驗證器伺服器來批准用戶的請求。以下是驗證器服務的 Python 實現示例:

# verifier_server.py
# 驗證器伺服器實現

import json
import time
import hashlib
from eth_account import Account
from eth_typing import ChecksumAddress
from web3 import Web3
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel
from typing import Optional
import secp256k1

app = FastAPI(title="Paymaster Verifier Server")

# 驗證器私鑰(應安全存儲,不要硬編碼)
VERIFIER_PRIVATE_KEY = "0x..."

# 服務商錢包
verifier_account = Account.from_key(VERIFIER_PRIVATE_KEY)

# 用戶Gas額度存儲
user_gas_limits = {}

# 風控規則配置
RATE_LIMIT_PER_USER = 100  # 每小時最多100次
GAS_LIMIT_DEFAULT = 300000
GAS_LIMIT_MAX = 1000000

class ApprovalRequest(BaseModel):
    user_address: str
    chain_id: int
    token_address: Optional[str] = None
    max_fee_per_gas: Optional[int] = None

class ApprovalResponse(BaseModel):
    approved: bool
    signature: str
    gas_limit: int
    expires_at: int
    reason: Optional[str] = None

def create_approval_signature(
    user_address: ChecksumAddress,
    chain_id: int,
    gas_limit: int,
    nonce: int,
    expiry: int
) -> str:
    """創建批准簽名"""
    
    # 構造簽名消息
    message = {
        "user": user_address,
        "chainId": chain_id,
        "gasLimit": gas_limit,
        "nonce": nonce,
        "expiry": expiry
    }
    
    # EIP-712 類型化數據簽名
    domain = {
        "name": "SignaturePaymaster",
        "version": "1",
        "chainId": chain_id,
        "verifyingContract": PAYMASTER_ADDRESS
    }
    
    types = {
        "Approval": [
            {"name": "user", "type": "address"},
            {"name": "chainId", "type": "uint256"},
            {"name": "gasLimit", "type": "uint256"},
            {"name": "nonce", "type": "uint256"},
            {"name": "expiry", "type": "uint256"]
        ]
    }
    
    # 生成簽名
    signature = verifier_account.sign_typed_data(
        domain=domain,
        message_types=types,
        message=message
    )
    
    return signature.hex()

def check_rate_limit(user_address: str) -> bool:
    """檢查用戶速率限制"""
    # 實現速率限制邏輯
    return True

def check_kyc_status(user_address: str) -> bool:
    """檢查用戶 KYC 狀態"""
    # 實現 KYC 檢查邏輯
    return True

@app.post("/approve", response_model=ApprovalResponse)
async def approve_request(
    request: ApprovalRequest,
    x_api_key: str = Header(None)
):
    """批准用戶的 Gas 資助請求"""
    
    # 驗證 API Key
    if not verify_api_key(x_api_key):
        raise HTTPException(status_code=401, detail="Invalid API key")
    
    # 風控檢查
    if not check_rate_limit(request.user_address):
        return ApprovalResponse(
            approved=False,
            signature="",
            gas_limit=0,
            expires_at=0,
            reason="Rate limit exceeded"
        )
    
    # KYC 檢查(可選)
    if not check_kyc_status(request.user_address):
        return ApprovalResponse(
            approved=False,
            signature="",
            gas_limit=0,
            expires_at=0,
            reason="KYC required"
        )
    
    # 確定 Gas 限額
    gas_limit = user_gas_limits.get(request.user_address, GAS_LIMIT_DEFAULT)
    gas_limit = min(gas_limit, GAS_LIMIT_MAX)
    
    # 生成批准
    nonce = int(time.time())
    expiry = int(time.time()) + 3600  # 1小時過期
    
    signature = create_approval_signature(
        user_address=Web3.to_checksum_address(request.user_address),
        chain_id=request.chain_id,
        gas_limit=gas_limit,
        nonce=nonce,
        expiry=expiry
    )
    
    return ApprovalResponse(
        approved=True,
        signature=signature,
        gas_limit=gas_limit,
        expires_at=expiry
    )

@app.post("/set-gas-limit")
async def set_user_gas_limit(
    user_address: str,
    gas_limit: int,
    api_key: str = Header(None)
):
    """設置用戶的 Gas 限額"""
    if not verify_api_key(api_key):
        raise HTTPException(status_code=401, detail="Invalid API key")
    
    if gas_limit > GAS_LIMIT_MAX:
        raise HTTPException(status_code=400, detail="Gas limit too high")
    
    user_gas_limits[user_address] = gas_limit
    return {"success": True, "gas_limit": gas_limit}

def verify_api_key(api_key: str) -> bool:
    """驗證 API Key"""
    # 實現 API Key 驗證邏輯
    return True

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

2.3 前端集成示例

錢包前端需要整合 Paymaster 功能,引導用戶獲取批准並構造正確的 UserOperation:

// paymaster-integration.js
// 前端 Paymaster 集成示例

import { ethers } from 'ethers';
import { UserOperation } from 'userop';

class PaymasterClient {
    constructor(config) {
        this.paymasterAddress = config.paymasterAddress;
        this.entryPointAddress = config.entryPointAddress;
        this.verifierUrl = config.verifierUrl;
        this.apiKey = config.apiKey;
    }

    /**
     * 獲取 Paymaster 批准
     */
    async getApproval(userAddress, chainId) {
        const response = await fetch(`${this.verifierUrl}/approve`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-API-Key': this.apiKey
            },
            body: JSON.stringify({
                user_address: userAddress,
                chain_id: chainId
            })
        });

        const result = await response.json();
        
        if (!result.approved) {
            throw new Error(`Approval denied: ${result.reason}`);
        }

        return {
            signature: result.signature,
            gasLimit: result.gas_limit,
            expiresAt: result.expires_at
        };
    }

    /**
     * 構造包含 Paymaster 的 UserOperation
     */
    async buildUserOperation(sender, callData, nonce, approval) {
        const userOp = {
            sender: sender,
            nonce: nonce,
            initCode: '0x',
            callData: callData,
            callGasLimit: approval.gasLimit,
            verificationGasLimit: 150000,
            preVerificationGas: 21000,
            maxFeePerGas: await this.getMaxFeePerGas(),
            maxPriorityFeePerGas: await this.getMaxPriorityFeePerGas(),
            paymasterAndData: ethers.solidityPacked(
                ['address', 'bytes'],
                [
                    this.paymasterAddress,
                    approval.signature
                ]
            ),
            signature: '0x' // 待用戶簽名
        };

        return userOp;
    }

    /**
     * 估算 Gas 費用
     */
    async estimateUserOpGas(userOp) {
        const provider = new ethers.JsonRpcProvider(RPC_URL);
        const entryPoint = new ethers.Contract(
            this.entryPointAddress,
            ENTRYPOINT_ABI,
            provider
        );

        const gasEstimate = await entryPoint.estimateGas.userOpGas(
            userOp,
            userOp.sender
        );

        return {
            callGasLimit: gasEstimate.callGasLimit,
            verificationGasLimit: gasEstimate.verificationGasLimit,
            preVerificationGas: gasEstimate.preVerificationGas
        };
    }

    /**
     * 發送 UserOperation
     */
    async sendUserOperation(userOp, signature) {
        const signedOp = {
            ...userOp,
            signature: signature
        };

        const provider = new ethers.JsonRpcProvider(RPC_URL);
        const entryPoint = new ethers.Contract(
            this.entryPointAddress,
            ENTRYPOINT_ABI,
            provider
        );

        const tx = await entryPoint.handleOps([signedOp], this.paymasterAddress);
        return await tx.wait();
    }

    async getMaxFeePerGas() {
        const provider = new ethers.JsonRpcProvider(RPC_URL);
        const block = await provider.getBlock();
        return block.baseFeePerGas * 2n + await this.getMaxPriorityFeePerGas();
    }

    async getMaxPriorityFeePerGas() {
        return ethers.parseUnits('2', 'gwei');
    }
}

// 使用示例
async function main() {
    const client = new PaymasterClient({
        paymasterAddress: '0x...',
        entryPointAddress: '0x5FF137D4b0FD9CDe4383A0a2d4E4D0f5D5a8f5F3',
        verifierUrl: 'http://localhost:8000',
        apiKey: 'your-api-key'
    });

    // 1. 獲取批准
    const approval = await client.getApproval(
        '0xUserAddress...',
        1 // Ethereum mainnet
    );

    // 2. 構造交易數據
    const wallet = new ethers.Wallet(privateKey);
    const callData = wallet.interface.encodeFunctionData('execute', [
        target,
        amount,
        '0x'
    ]);

    // 3. 構造 UserOperation
    const userOp = await client.buildUserOperation(
        wallet.address,
        callData,
        0, // nonce
        approval
    );

    // 4. 估算 Gas
    const gasInfo = await client.estimateUserOpGas(userOp);
    userOp.callGasLimit = gasInfo.callGasLimit;

    // 5. 用戶簽名
    const signature = await wallet.signMessage(
        ethers.getMessageHash(ethers.serializeTransaction({
            to: userOp.sender,
            nonce: userOp.nonce,
            data: userOp.callData
        }))
    );

    // 6. 發送交易
    const receipt = await client.sendUserOperation(userOp, signature);
    console.log('Transaction confirmed:', receipt.hash);
}

三、代幣 Paymaster 完整實作

3.1 設計原理

代幣 Paymaster 允許用戶使用任何 ERC-20 代幣支付 Gas 費用。這種設計對於不持有 ETH 但持有其他代幣的用戶特別有用。代幣 Paymaster 的核心邏輯包括:用戶發起使用代幣支付 Gas 的 UserOperation;Paymaster 驗證用戶的代幣餘額是否足夠;Paymaster 計算應該扣除的代幣數量(基於 Gas 費用和匯率);EntryPoint 執行操作並從用戶的代幣餘額中轉帳;Paymaster 收到代幣作為報酬。

以下是一個完整的代幣 Paymaster 實現:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/IEntryPoint.sol";

/**
 * @title TokenPaymaster
 * @dev 代幣 Paymaster 實現
 * 
 * 功能:
 * - 用戶使用 ERC-20 代幣支付 Gas 費用
 * - 支持多種代幣
 * - 可配置的匯率和費用
 */
contract TokenPaymaster is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    IEntryPoint public immutable entryPoint;
    
    // 支持的代幣列表
    struct TokenConfig {
        bool enabled;
        uint256 priceMarkup;  // 溢價百分比
        bool isNative;        // 是否是 ETH
    }
    
    mapping(address => TokenConfig) public supportedTokens;
    address[] public tokenList;
    
    // 代幣兌換價格(每 ETH 多少代幣)
    mapping(address => uint256) public exchangeRates;
    
    // 代幣精度
    mapping(address => uint8) public tokenDecimals;
    
    // 運營商地址
    address public operator;
    
    // Paymaster 存款
    uint256 public deposit;
    
    // 事件
    event TokenAdded(address indexed token, uint256 priceMarkup);
    event TokenRemoved(address indexed token);
    event ExchangeRateUpdated(address indexed token, uint256 rate);
    event GasPaidWithToken(
        address indexed user,
        address indexed token,
        uint256 tokenAmount,
        uint256 gasCost
    );
    event DepositReceived(address indexed user, uint256 amount);

    modifier onlyOperator() {
        require(msg.sender == operator, "Not operator");
        _;
    }

    constructor(address _entryPoint, address _owner) {
        require(_entryPoint != address(0), "Invalid EntryPoint");
        entryPoint = IEntryPoint(_entryPoint);
        operator = _owner;
        transferOwnership(_owner);
    }

    /**
     * @dev 驗證 UserOperation
     */
    function validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 requestId,
        uint256 requiredPreFund
    ) external override returns (bytes memory context) {
        require(msg.sender == address(entryPoint), "Not EntryPoint");

        // 解析 UserOperation 中的代幣信息
        (address token, bytes memory approvalSignature) = abi.decode(
            userOp.paymasterAndData[20:],
            (address, bytes)
        );

        // 驗證代幣是否支持
        require(supportedTokens[token].enabled, "Token not supported");

        // 計算需要的代幣數量
        uint256 tokenAmount = _calculateTokenAmount(requiredPreFund, token);
        
        // 檢查用戶代幣餘額
        IERC20 tokenContract = IERC20(token);
        uint256 balance = tokenContract.balanceOf(userOp.sender);
        require(balance >= tokenAmount, "Insufficient token balance");

        // 檢查是否已有授權
        uint256 allowance = tokenContract.allowance(userOp.sender, address(this));
        require(allowance >= tokenAmount, "Insufficient allowance");

        // 記錄代幣信息到上下文
        context = abi.encode(token, tokenAmount, requiredPreFund);
    }

    /**
     * @dev 主操作執行後的回調
     */
    function postOp(
        PostOpMode mode,
        bytes calldata context,
        uint256 actualGasCost
    ) external override nonReentrant {
        require(msg.sender == address(entryPoint), "Not EntryPoint");

        (address token, uint256 tokenAmount, uint256 preFund) = abi.decode(
            context,
            (address, uint256, uint256)
        );

        if (mode == PostOpMode.opMode) {
            // 主操作成功,扣除代幣
            IERC20 tokenContract = IERC20(token);
            
            // 實際 Gas 費用可能與預估算不同,退還差額
            uint256 actualTokenAmount = _calculateTokenAmount(actualGasCost, token);
            uint256 refund = tokenAmount - actualTokenAmount;
            
            // 轉帳代幣
            tokenContract.safeTransferFrom(
                userOp.sender,  // 從上下文或全局變量獲取
                address(this),
                actualTokenAmount
            );
            
            emit GasPaidWithToken(userOp.sender, token, actualGasCost, actualTokenAmount);
        }
        // 如果操作失敗,不需要扣取代幣(preFund 會被退還)
    }

    /**
     * @dev 計算代幣數量
     */
    function _calculateTokenAmount(uint256 ethAmount, address token) 
        internal 
        view 
        returns (uint256) 
    {
        uint256 rate = exchangeRates[token];
        require(rate > 0, "Exchange rate not set");
        
        uint8 decimals = tokenDecimals[token];
        uint256 markup = supportedTokens[token].priceMarkup;
        
        // 計算代幣數量:ethAmount * rate * markup / 100
        uint256 tokenAmount = ethAmount * rate / 1e18;
        tokenAmount = tokenAmount * markup / 100;
        
        // 處理精度
        if (decimals < 18) {
            tokenAmount = tokenAmount * (10 ** (18 - decimals));
        } else if (decimals > 18) {
            tokenAmount = tokenAmount / (10 ** (decimals - 18));
        }
        
        return tokenAmount;
    }

    /**
     * @dev 添加支持的代幣
     */
    function addToken(
        address _token,
        uint256 _priceMarkup,
        uint256 _exchangeRate,
        uint8 _decimals
    ) external onlyOwner {
        require(_token != address(0), "Invalid token");
        require(_priceMarkup >= 100 && _priceMarkup <= 150, "Invalid markup");
        
        supportedTokens[_token] = TokenConfig({
            enabled: true,
            priceMarkup: _priceMarkup,
            isNative: false
        });
        
        exchangeRates[_token] = _exchangeRate;
        tokenDecimals[_token] = _decimals;
        tokenList.push(_token);
        
        emit TokenAdded(_token, _priceMarkup);
    }

    /**
     * @dev 移除代幣
     */
    function removeToken(address _token) external onlyOwner {
        require(supportedTokens[_token].enabled, "Token not supported");
        
        supportedTokens[_token].enabled = false;
        
        emit TokenRemoved(_token);
    }

    /**
     * @dev 更新匯率
     */
    function updateExchangeRate(address _token, uint256 _rate) external onlyOwner {
        require(supportedTokens[_token].enabled, "Token not supported");
        require(_rate > 0, "Invalid rate");
        
        exchangeRates[_token] = _rate;
        
        emit ExchangeRateUpdated(_token, _rate);
    }

    /**
     * @dev 批量更新匯率
     */
    function updateExchangeRates(
        address[] calldata _tokens,
        uint256[] calldata _rates
    ) external onlyOwner {
        require(_tokens.length == _rates.length, "Length mismatch");
        
        for (uint256 i = 0; i < _tokens.length; i++) {
            require(supportedTokens[_tokens[i]].enabled, "Token not supported");
            require(_rates[i] > 0, "Invalid rate");
            
            exchangeRates[_tokens[i]] = _rates[i];
            emit ExchangeRateUpdated(_tokens[i], _rates[i]);
        }
    }

    /**
     * @dev 存款
     */
    function deposit() external payable {
        deposit += msg.value;
        emit DepositReceived(msg.sender, msg.value);
    }

    /**
     * @dev 提取收益
     */
    function withdrawToken(address _token, uint256 _amount) external onlyOwner {
        IERC20 tokenContract = IERC20(_token);
        tokenContract.safeTransfer(operator, _amount);
    }

    /**
     * @dev 提取 ETH
     */
    function withdrawEth(uint256 _amount) external onlyOwner {
        require(_amount <= deposit, "Insufficient balance");
        deposit -= _amount;
        payable(operator).transfer(_amount);
    }

    /**
     * @dev 估算代幣費用
     */
    function estimateTokenCost(
        address _token,
        uint256 _gasLimit
    ) external view returns (uint256) {
        require(supportedTokens[_token].enabled, "Token not supported");
        
        uint256 ethCost = _gasLimit * block.gasprice;
        return _calculateTokenAmount(ethCost, _token);
    }

    /**
     * @dev 獲取支持代幣列表
     */
    function getSupportedTokens() external view returns (address[] memory) {
        uint256 count = 0;
        for (uint256 i = 0; i < tokenList.length; i++) {
            if (supportedTokens[tokenList[i]].enabled) {
                count++;
            }
        }
        
        address[] memory result = new address[](count);
        uint256 index = 0;
        for (uint256 i = 0; i < tokenList.length; i++) {
            if (supportedTokens[tokenList[i]].enabled) {
                result[index] = tokenList[i];
                index++;
            }
        }
        
        return result;
    }

    // 接收 ETH
    receive() external payable {
        deposit += msg.value;
    }
}

四、條件式 Paymaster 實作

4.1 基於餘額的條件邏輯

條件式 Paymaster 可以根據用戶的存款餘額自動決定是否支付費用。以下是一個基於餘額閾值的實現:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/IEntryPoint.sol";

/**
 * @title ConditionalPaymaster
 * @dev 條件式 Paymaster 實現
 * 
 * 根據預定義條件自動決定是否支付費用:
 * - 用戶存款餘額超過閾值
 * - 操作涉及特定合約
 * - 網路費用在合理範圍內
 */
contract ConditionalPaymaster is Ownable, ReentrancyGuard {

    IEntryPoint public immutable entryPoint;
    
    // 條件配置
    struct ConditionConfig {
        bool enabled;
        address targetContract;  // 目標合約地址
        uint256 minBalance;       // 最低餘額要求
        uint256 maxGasPrice;      // 最高 Gas 價格
    }
    
    mapping(bytes32 => ConditionConfig) public conditions;
    bytes32[] public conditionList;
    
    // 全局配置
    uint256 public defaultMinBalance = 0.1 ether;  // 默認最低餘額 0.1 ETH
    uint256 public defaultMaxGasPrice = 100 gwei;   // 默認最高 Gas 價格
    
    // 白名單
    mapping(address => bool) public whitelist;
    mapping(address => uint256) public whitelistGasLimit;
    
    // 費用參數
    uint256 public priceMarkup = 120;  // 費用溢價 120%
    
    // 事件
    event ConditionCreated(bytes32 indexed conditionId, ConditionConfig config);
    event ConditionUpdated(bytes32 indexed conditionId, ConditionConfig config);
    event WhitelistUpdated(address indexed user, bool status, uint256 gasLimit);

    constructor(address _entryPoint, address _owner) {
        require(_entryPoint != address(0), "Invalid EntryPoint");
        entryPoint = IEntryPoint(_entryPoint);
        transferOwnership(_owner);
    }

    /**
     * @dev 驗證 UserOperation
     */
    function validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 requestId,
        uint256 requiredPreFund
    ) external override returns (bytes memory context) {
        require(msg.sender == address(entryPoint), "Not EntryPoint");

        // 解析目標合約
        address target = _parseTarget(userOp.callData);
        
        // 檢查白名單
        if (whitelist[userOp.sender]) {
            // 白名單用戶,根據其 Gas 限額處理
            uint256 gasLimit = whitelistGasLimit[userOp.sender];
            require(
                requiredPreFund <= gasLimit * block.gasprice * priceMarkup / 100,
                "Exceeds gas limit"
            );
            context = abi.encode(userOp.sender, true);
            return context;
        }

        // 檢查條件
        bytes32 conditionId = keccak256(abi.encodePacked(target));
        ConditionConfig memory condition = conditions[conditionId];
        
        if (condition.enabled) {
            // 有特定條件,檢查目標合約
            require(
                condition.targetContract == target,
                "Target not allowed"
            );
            require(
                block.gasprice <= condition.maxGasPrice,
                "Gas price too high"
            );
        } else {
            // 默認條件:檢查用戶餘額
            require(
                userOp.sender.balance >= defaultMinBalance,
                "Insufficient balance"
            );
            require(
                block.gasprice <= defaultMaxGasPrice,
                "Gas price too high"
            );
        }

        context = abi.encode(userOp.sender, false);
    }

    /**
     * @dev 解析目標合約地址
     */
    function _parseTarget(bytes memory callData) internal pure returns (address) {
        if (callData.length >= 68) {
            // 標準合約調用格式
            bytes4 selector;
            assembly {
                selector := mload(add(callData, 32))
            }
            
            // 對於常見的 transfer、swap 等操作,嘗試解析目標
            if (callData.length >= 100) {
                address target;
                assembly {
                    target := mload(add(callData, 36))
                }
                return target;
            }
        }
        return address(0);
    }

    /**
     * @dev 主操作執行後的回調
     */
    function postOp(
        PostOpMode mode,
        bytes calldata context,
        uint256 actualGasCost
    ) external override nonReentrant {
        require(msg.sender == address(entryPoint), "Not EntryPoint");
        
        // 條件式 Paymaster 通常不需要後處理
        // 可以記錄日誌或觸發其他邏輯
    }

    /**
     * @dev 創建條件
     */
    function createCondition(
        address _targetContract,
        uint256 _minBalance,
        uint256 _maxGasPrice
    ) external onlyOwner returns (bytes32) {
        require(_targetContract != address(0), "Invalid target");
        
        bytes32 conditionId = keccak256(abi.encodePacked(_targetContract));
        
        conditions[conditionId] = ConditionConfig({
            enabled: true,
            targetContract: _targetContract,
            minBalance: _minBalance,
            maxGasPrice: _maxGasPrice
        });
        
        conditionList.push(conditionId);
        
        emit ConditionCreated(conditionId, conditions[conditionId]);
        
        return conditionId;
    }

    /**
     * @dev 更新條件
     */
    function updateCondition(
        bytes32 _conditionId,
        uint256 _minBalance,
        uint256 _maxGasPrice,
        bool _enabled
    ) external onlyOwner {
        ConditionConfig storage config = conditions[_conditionId];
        require(config.targetContract != address(0), "Condition not exists");
        
        config.minBalance = _minBalance;
        config.maxGasPrice = _maxGasPrice;
        config.enabled = _enabled;
        
        emit ConditionUpdated(_conditionId, config);
    }

    /**
     * @dev 添加到白名單
     */
    function addToWhitelist(
        address[] calldata _users,
        uint256[] calldata _gasLimits
    ) external onlyOwner {
        require(_users.length == _gasLimits.length, "Length mismatch");
        
        for (uint256 i = 0; i < _users.length; i++) {
            whitelist[_users[i]] = true;
            whitelistGasLimit[_users[i]] = _gasLimits[i];
            emit WhitelistUpdated(_users[i], true, _gasLimits[i]);
        }
    }

    /**
     * @dev 從白名單移除
     */
    function removeFromWhitelist(address _user) external onlyOwner {
        whitelist[_user] = false;
        whitelistGasLimit[_user] = 0;
        emit WhitelistUpdated(_user, false, 0);
    }

    /**
     * @dev 設置默認參數
     */
    function setDefaultParams(uint256 _minBalance, uint256 _maxGasPrice) 
        external 
        onlyOwner 
    {
        defaultMinBalance = _minBalance;
        defaultMaxGasPrice = _maxGasPrice;
    }

    /**
     * @dev 設置費用溢價
     */
    function setPriceMarkup(uint256 _markup) external onlyOwner {
        require(_markup >= 100 && _markup <= 200, "Invalid markup");
        priceMarkup = _markup;
    }
}

五、部署與運營最佳實踐

5.1 合約部署清單

在部署 Paymaster 合約之前,需要完成以下準備工作:

安全性審計:Paymaster 處理資金,必須經過專業安全審計。常見的審計焦點包括:簽名驗證邏輯是否有漏洞;整數溢出和下溢風險;重入攻擊防護;訪問控制是否正確實施。

初始化配置:部署時需要配置的關鍵參數包括:EntryPoint 合約地址(以太坊主網為 0x5FF137D4b0FD9CDe4383A0a2d4E4D0f5D5a8f5F3);運營商地址;初始存款金額(建議至少 10 ETH 起步)。

Gas 費用預算:評估預期的交易量並準備足夠的 ETH 存款。典型的 Paymaster 運營成本計算公式為:月均交易量 × 平均 Gas 費用 × 安全係數(建議 1.5 倍)。

5.2 運維監控要點

成功的 Paymaster 運營需要持續監控以下指標:

存款餘額:Paymaster 合約的 ETH 餘額不應低於某個閾值(建議維持在平均月費用的 2 倍以上)。餘額不足時需要及時補充。

交易成功率:監控 validatePaymasterUserOp 和 postOp 的成功率。失敗可能表示配置錯誤或遭受攻擊。

Gas 價格波動:在高 Gas 時期,Paymaster 的成本會顯著增加。可能需要動態調整費用溢價或暫停服務。

異常模式檢測:監控是否有異常的交易模式,如大量小額交易、來自同一地址的密集請求等,這可能表示遭受攻擊或濫用。

5.3 安全建議

多簽管理:Paymaster 的管理權限應使用多簽錢包,避免單點故障。建議使用 3-of-5 或 5-of-9 的 Gnosis Safe 配置。

速率限制:實施用戶級和全局速率限制,防止 DoS 攻擊和資源耗盡。

緊急暫停機制:實現 Pauable 模式,在發現異常時可以暫停合約。

定期安全審計:每次重大更新後都應重新審計。

監控和警報:部署專業的區塊鏈監控系統,設置異常交易警報。

六、結論與展望

Paymaster 是 ERC-4337 帳戶抽象生態系統中最具實用價值的組件之一。它解決了區塊鏈應用進入門檻高的核心問題,讓用戶無需持有 ETH 即可使用以太坊網路。隨著智慧合約錢包的普及,Paymaster 的採用將持續增長。

本文詳細介紹了三種主要的 Paymaster 實現類型:基於簽名的 Paymaster 適合需要審批場景;條件式 Paymaster 適合自動化風控;代幣 Paymaster 適合讓用戶使用各種代幣支付費用。開發者可以根據實際需求選擇或組合這些模式。

未來,隨著 EIP-7702 的部署和以太坊帳戶模型的持續演進,Paymaster 的形態也可能發生變化。但無論技術如何發展,降低用戶進入門檻、提升用戶體驗的核心目標將始終是區塊鏈應用發展的重要方向。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!