Skip to main content

Gate Chain 账户抽象 Bundler API 集成指南

本文档介绍如何使用 ERC-4337 账户抽象标准,向 Gate Chain Bundler 提交 UserOperation

核心目标:构造 UserOperation,获取 Gas 估算,完成签名,并通过 Gate Chain Bundler 将其提交上链。


1. 网络参数

Gate Chain 主网

参数
Chain ID86
RPC(HTTP,推荐)https://evm.nodeinfo.cc
RPC(HTTP,备用)https://evm-1.nodeinfo.cc / https://evm.gatenode.cc
RPC(WSS)wss://evm-ws.gatenode.cc
Bundlerhttps://gatechain-bundler.gatenode.cc
EntryPoint v0.8通过 eth_supportedEntryPoints 查询(标准地址:0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108
区块浏览器https://www.gatescan.org/gatechain
Gas 代币GT
AA 标准ERC-4337 v0.7

Gate Chain 测试网(Meteora)

参数
Chain ID85
RPC(HTTP)https://meteora-evm.gatenode.cc
RPC(WSS)wss://meteora-ws.gatenode.cc
Bundlerhttps://gatechain-meteora-bundler.gatenode.cc
区块浏览器https://www.gatescan.org/gatechain-testnet

2. 关于 Gate Chain

Gate Chain 是一条兼容 EVM 的 Layer 1 区块链。

Gate Chain 作为 Gate Layer L2 的结算层。两个网络使用相同的 AA 集成方式,但在网络参数和 Gas 费用模型上有所不同——详见第 4 节。


3. ERC-4337 概念

ERC-4337 允许智能合约账户在无需 EOA 的情况下发起交易。用户不再提交原始交易,而是向 Bundler 提交 UserOperation 对象。

标准执行流程:

  1. 用户或 dApp 构造并签名一个 UserOperation
  2. Bundler 从替代内存池(alt-mempool)收集 UserOperation
  3. Bundler 将它们打包成一笔交易
  4. 该交易被发送至 EntryPoint 合约
  5. EntryPoint 验证并执行每个操作

Paymaster 为可选项。paymaster 为空时,智能账户从其自身的 EntryPoint 存款中支付 Gas(需提前通过 depositTo() 充值)。当 paymaster 有值时,Paymaster 合约代替用户赞助 Gas。


4. Gas 费用机制

Gate Chain 是 Layer 1 网络,使用标准 EIP-1559 Gas 模型。

每笔交易的费用计算公式为:

totalFee = gasUsed × (baseFee + priorityFee)
组成部分说明
baseFee网络基础费,每个区块动态调整(EIP-1559)
priorityFee给验证者的可选小费,可为零或极小值

对于 UserOperation,preVerificationGas 主要覆盖 Bundler 的 calldata 开销,callGasLimitverificationGasLimit 基于实际的链上执行成本。请始终使用 eth_estimateUserOperationGas 获取准确值,而非手动估算。


5. EntryPoint 合约地址

查询 Bundler 以获取当前活跃的 EntryPoint 地址:

curl -s "<GATECHAIN_BUNDLER_ENDPOINT>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_supportedEntryPoints",
"params": []
}'

返回示例:

{
"jsonrpc": "2.0",
"id": 1,
"result": ["0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"]
}

始终在运行时通过 eth_supportedEntryPoints 动态获取 EntryPoint 地址,不要硬编码。不同版本的地址不同,硬编码会导致静默验证失败。


6. UserOperation 字段说明(v0.7)

ERC-4337 v0.7 将 initCodepaymasterAndData 拆分为独立字段。

字段类型说明
senderaddress智能账户地址
nonceuint256EntryPoint.getNonce(sender, key) 获取
factoryaddress账户工厂地址(仅首次部署时填写,已部署则省略)
factoryDatabytes工厂调用数据(仅首次部署时填写,已部署则设为 0x
callDatabytes在智能账户上执行的调用编码数据
callGasLimituint256执行阶段的 Gas 上限
verificationGasLimituint256验证阶段的 Gas 上限
preVerificationGasuint256覆盖 Bundler 打包成本的开销 Gas
maxFeePerGasuint256最高 Gas 价格(baseFee + priorityFee)
maxPriorityFeePerGasuint256给验证者的最高小费
paymasteraddressPaymaster 合约地址;自付时留空
paymasterVerificationGasLimituint256Paymaster 验证阶段的 Gas(无 Paymaster 时设为 0x0
paymasterPostOpGasLimituint256Paymaster 后处理阶段的 Gas(无 Paymaster 时设为 0x0
paymasterDatabytesPaymaster 专属数据(无 Paymaster 时设为 0x
signaturebytes账户所有者对 UserOperation 哈希的签名
eip7702Authobject(可选) EIP-7702 授权元组——详见第 10 节

7. 集成流程

第 1 步 — 查询 EntryPoint

curl -s "<GATECHAIN_BUNDLER_ENDPOINT>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_supportedEntryPoints","params":[]}'

第 2 步 — 获取优先费

curl -s "<GATECHAIN_NODE_RPC>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"eth_maxPriorityFeePerGas","params":[]}'

将返回值作为 maxPriorityFeePerGas,加上当前 baseFee 即得 maxFeePerGas

通过 Gate Chain RPC 获取当前 baseFee

curl -s "<GATECHAIN_NODE_RPC>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]}'

从响应中读取 baseFeePerGas 字段。

第 3 步 — 估算 Gas

将所有 Gas 字段设为 0x0,并使用占位签名(dummy signature)发送 UserOperation:

curl -s "<GATECHAIN_BUNDLER_ENDPOINT>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "eth_estimateUserOperationGas",
"params": [
{
"sender": "<SENDER_ADDRESS>",
"nonce": "<NONCE_HEX>",
"callData": "<CALL_DATA_HEX>",
"callGasLimit": "0x0",
"verificationGasLimit": "0x0",
"preVerificationGas": "0x0",
"maxPriorityFeePerGas": "<MAX_PRIORITY_FEE_HEX>",
"maxFeePerGas": "<MAX_FEE_HEX>",
"signature": "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000011b"
},
"<ENTRY_POINT_ADDRESS>"
]
}'

当不需要 factory 或 paymaster 时,请完全省略这些字段。将地址字段设为空字符串("")会导致 Bundler 返回 Invalid params 错误。

关于占位签名(dummy signature):Gas 估算需要一个格式有效的 65 字节 ECDSA 签名。传入空的 0x 会导致 ECDSA.recover() 回滚,从而破坏估算流程。请使用上述占位签名,或任何格式有效、但不匹配账户所有者密钥的 65 字节十六进制值。

返回示例:

{
"jsonrpc": "2.0",
"id": 3,
"result": {
"preVerificationGas": "<HEX>",
"verificationGasLimit": "<HEX>",
"callGasLimit": "<HEX>",
"paymasterVerificationGasLimit": "0x0"
}
}

第 4 步 — 签名

计算 UserOperation 哈希,并用账户所有者私钥签名:

userOpHash = keccak256(abi.encode(
keccak256(packed(userOp fields)),
entryPointAddress,
chainId // GateChain 主网:86
))
signature = owner.signMessage(userOpHash) // 需要 ETH 签名消息前缀

第 5 步 — 提交

curl -s "<GATECHAIN_BUNDLER_ENDPOINT>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 4,
"method": "eth_sendUserOperation",
"params": [
{
"sender": "<SENDER_ADDRESS>",
"nonce": "<NONCE_HEX>",
"callData": "<CALL_DATA_HEX>",
"callGasLimit": "<CALL_GAS_LIMIT_HEX>",
"verificationGasLimit": "<VERIFICATION_GAS_LIMIT_HEX>",
"preVerificationGas": "<PRE_VERIFICATION_GAS_HEX>",
"maxPriorityFeePerGas": "<MAX_PRIORITY_FEE_HEX>",
"maxFeePerGas": "<MAX_FEE_HEX>",
"signature": "<SIGNED_USER_OPERATION_HEX>"
},
"<ENTRY_POINT_ADDRESS>"
]
}'

返回示例:

{
"jsonrpc": "2.0",
"id": 4,
"result": "<USER_OPERATION_HASH>"
}

第 6 步 — 轮询回执

curl -s "<GATECHAIN_BUNDLER_ENDPOINT>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 5,
"method": "eth_getUserOperationReceipt",
"params": ["<USER_OPERATION_HASH>"]
}'

待确认时返回 null。建议每 3–5 秒轮询一次,并采用指数退避策略。确认后的结果包含 transactionHash"success": true

GateChain 出块时间约为 4-30 秒。


8. 自付模式 vs. Paymaster 模式

自付模式(无 Paymaster)

从 UserOperation 中完全省略所有 paymaster 和 factory 字段。不要将地址字段设为空字符串("")——Bundler 会拒绝请求。直接从 JSON 对象中去掉这些字段即可。

提交前,请确保智能账户在 EntryPoint 中有足够的 GT 存款:

entryPoint.depositTo{value: amount}(smartAccountAddress);

Paymaster 赞助模式

当 Paymaster 赞助 Gas 时,填写以下字段:

"paymaster": "<PAYMASTER_CONTRACT_ADDRESS>",
"paymasterVerificationGasLimit": "<HEX>",
"paymasterPostOpGasLimit": "<HEX>",
"paymasterData": "<PAYMASTER_SIGNED_DATA_HEX>"

paymasterData 由 Paymaster 服务在验证并联署 UserOperation 后提供。


9. SDK 快速入门(quick-start.js)

在生产环境中,quick-start.js 对原始 JSON-RPC 流程进行了封装。

安装依赖

npm install viem @aa-sdk/core @account-kit/smart-contracts

定义 Gate Chain 链

import { LocalAccountSigner, getEntryPoint } from '@aa-sdk/core';
import { createLightAccount } from '@account-kit/smart-contracts';
import {
createPublicClient,
defineChain,
http,
parseEther,
toHex,
encodeFunctionData,
getContract,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const {
PRIVATE_KEY,
RPC_URL = 'https://evm.nodeinfo.cc',
BUNDLER_URL = 'https://gatechain-bundler.gatenode.cc',
ENTRY_POINT = '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
LIGHT_ACCOUNT_FACTORY = '0x13E9ed32155810FDbd067D4522C492D6f68E5944',
RECIPIENT_ADDRESS = '0xRECIPIENT_ADDRESS',
CHAIN_ID = '86',
TRANSFER_GT = '0.001',
} = process.env;

if (!PRIVATE_KEY) throw new Error('Missing PRIVATE_KEY');

const chain = defineChain({
id: Number(CHAIN_ID),
name: `GateChain-${CHAIN_ID}`,
nativeCurrency: { name: 'GT', symbol: 'GT', decimals: 18 },
rpcUrls: { default: { http: [RPC_URL] } },
});

const publicClient = createPublicClient({ chain, transport: http(RPC_URL) });

async function bundlerRpc(method, params) {
const res = await fetch(BUNDLER_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
});
const { result, error } = await res.json();
if (error) throw new Error(`${method}: ${JSON.stringify(error)}`);
return result;
}

async function main() {
// 1. 创建智能账户
const owner = LocalAccountSigner.privateKeyToAccountSigner(PRIVATE_KEY);
const ownerAddr = await owner.getAddress();
const entryPointDef = { ...getEntryPoint(chain, { version: '0.7.0' }), address: ENTRY_POINT };

// 向工厂查询正确的反事实地址
const factoryContract = getContract({
address: LIGHT_ACCOUNT_FACTORY,
abi: [
{
name: 'getAddress',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'salt', type: 'uint256' },
],
outputs: [{ name: '', type: 'address' }],
},
],
client: publicClient,
});
const accountAddress = await factoryContract.read.getAddress([ownerAddr, 0n]);

const account = await createLightAccount({
chain,
transport: http(RPC_URL),
signer: owner,
factoryAddress: LIGHT_ACCOUNT_FACTORY,
accountAddress,
version: 'v2.0.0',
entryPoint: entryPointDef,
});

const sender = account.address;
console.log('Smart account address:', sender);

// 2. 检查部署状态 & 获取 initCode
const code = await publicClient.getCode({ address: sender });
const isDeployed = code && code !== '0x';
const initCode = await account.getInitCode();
const needsDeploy = !isDeployed && initCode && initCode !== '0x';

// 3. 构建 callData
const callData = encodeFunctionData({
abi: [
{
name: 'execute',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'dest', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'func', type: 'bytes' },
],
outputs: [],
},
],
functionName: 'execute',
args: [RECIPIENT_ADDRESS, parseEther(TRANSFER_GT), '0x'],
});

// 4. 获取 nonce
const nonce = await publicClient.readContract({
address: ENTRY_POINT,
abi: entryPointDef.abi,
functionName: 'getNonce',
args: [sender, 0n],
});

// 5. Gas 限制 & 费用(需为整数 gwei)
// 注意:请根据实际交易复杂度调整这些值。
// 生产环境中应使用 eth_estimateUserOperationGas 获取精确值。
const callGasLimit = 200_000n;
const verificationGasLimit = 500_000n;
const preVerificationGas = 100_000n;
const maxPriorityFeePerGas = 1_000_000_000n; // 1 gwei
const maxFeePerGas = 20_000_000_000n; // 20 gwei

// 6. 通过 EntryPoint.getUserOpHash() 签名
const normalizedPk = PRIVATE_KEY.startsWith('0x') ? PRIVATE_KEY : `0x${PRIVATE_KEY}`;
const eoa = privateKeyToAccount(normalizedPk);
const packedForHash = {
sender,
nonce,
initCode: needsDeploy ? initCode : '0x',
callData,
accountGasLimits:
'0x' +
verificationGasLimit.toString(16).padStart(32, '0') +
callGasLimit.toString(16).padStart(32, '0'),
preVerificationGas,
gasFees:
'0x' +
maxPriorityFeePerGas.toString(16).padStart(32, '0') +
maxFeePerGas.toString(16).padStart(32, '0'),
paymasterAndData: '0x',
signature: '0x',
};
const userOpHash = await publicClient.readContract({
address: ENTRY_POINT,
abi: entryPointDef.abi,
functionName: 'getUserOpHash',
args: [packedForHash],
});
const signature = await eoa.sign({ hash: userOpHash });

// 7. 发送
const hash = await bundlerRpc('eth_sendUserOperation', [
{
sender,
nonce: toHex(nonce),
callData,
callGasLimit: toHex(callGasLimit),
verificationGasLimit: toHex(verificationGasLimit),
preVerificationGas: toHex(preVerificationGas),
maxPriorityFeePerGas: toHex(maxPriorityFeePerGas),
maxFeePerGas: toHex(maxFeePerGas),
signature,
...(needsDeploy
? { factory: initCode.slice(0, 42), factoryData: '0x' + initCode.slice(42) }
: {}),
},
ENTRY_POINT,
]);
console.log('UserOp hash:', hash);

// 8. 等待回执
let receipt;
for (let i = 0; i < 30; i++) {
receipt = await bundlerRpc('eth_getUserOperationReceipt', [hash]);
if (receipt?.receipt?.transactionHash) break;
await new Promise((r) => setTimeout(r, 4000));
}
console.log('Tx hash:', receipt.receipt.transactionHash);
}

main().catch((err) => {
console.error('Failed:', err);
process.exit(1);
});

10. EIP-7702 授权字段

EIP-7702 允许 EOA 在单笔交易中将其代码临时委托给智能合约。结合 ERC-4337 使用时,eip7702Auth 字段使 EOA 账户无需单独部署即可获得智能账户能力。

该字段为可选项,仅在 UserOperation 需要 EIP-7702 代码委托时填写。

字段结构:

"eip7702Auth": {
"chainId": "0x56",
"address": "<DELEGATED_CONTRACT_ADDRESS>",
"nonce": "<EOA_NONCE_HEX>",
"yParity": "<0x0 或 0x1>",
"r": "<SIGNATURE_R_HEX>",
"s": "<SIGNATURE_S_HEX>"
}

Gate Chain 主网的 chainId0x56(十进制 86)。当该字段存在时,Bundler 会将授权元组加入交易的 authorizationList,并以 EIP-7702 交易类型提交。UserOperation 的哈希计算也会纳入被委托的合约地址。


11. 智能账户部署

首次使用时,智能账户尚未在链上存在。账户地址是反事实的——由工厂地址和所有者地址确定性推导而来。

要在首次操作的同一笔交易中部署新智能账户,请填写 factoryfactoryData

"factory": "<ACCOUNT_FACTORY_ADDRESS>",
"factoryData": "<ENCODED_CONSTRUCTOR_CALLDATA>"

账户部署完成后(nonce > 0),后续操作省略这两个字段。

如果 factory 非空,EntryPoint 会在验证阶段调用 factory.createAccount(factoryData)。部署后的地址必须与 sender 一致,否则操作将被拒绝。


12. RPC 方法参考

Bundler 方法

方法说明
eth_supportedEntryPoints返回当前活跃的 EntryPoint 合约地址
eth_estimateUserOperationGas估算 UserOperation 的 Gas 限制
eth_sendUserOperation将已签名的 UserOperation 提交至内存池
eth_getUserOperationReceipt返回执行结果(待确认时返回 null)
eth_getUserOperationByHash根据哈希返回完整的 UserOperation 详情

标准 EVM 方法(调用 Gate Chain RPC)

方法说明
eth_getBlockByNumber获取最新区块以读取 baseFeePerGas
eth_call模拟合约调用(不发送交易)
eth_getTransactionReceipt查询链上交易回执

数值格式

请求和响应中的所有数值均使用带 0x 前缀的十六进制字符串。示例:gasLimit: 21000"0x5208"


13. 错误代码参考

代码名称原因解决方案
AA10Sender 已存在提供了 factory 但账户已部署移除 factoryfactoryData 字段
AA13Init code 失败账户创建期间工厂调用回滚检查工厂地址和 factoryData 编码
AA21未支付预付款智能账户在 EntryPoint 中存款不足提交前调用 entryPoint.depositTo()
AA23验证时回滚validateUserOp() 返回失败检查签名、nonce 和账户验证逻辑
AA25账户 nonce 无效nonce 不匹配重新查询 EntryPoint.getNonce() 并重建 UserOp
AA31Paymaster 存款不足Paymaster 在 EntryPoint 中余额不足补充 Paymaster 存款
AA33Paymaster 验证时回滚validatePaymasterUserOp() 失败检查 paymasterData 和 Paymaster 策略条件
AA40超出验证 Gas 上限verificationGasLimit 过低重新运行 eth_estimateUserOperationGas
AA51预付款低于实际 Gas 成本maxFeePerGas 低于当前网络水平重新查询 eth_maxPriorityFeePerGas 并重建 UserOp

14. 故障排查

EntryPoint 地址不匹配 — 始终在运行时通过 eth_supportedEntryPoints 动态获取 EntryPoint 地址。硬编码在版本不同时会导致静默验证失败。

Gas 估算失败eth_estimateUserOperationGas 报错的常见原因:callData 编码无效、账户余额不足以通过预付款检查,或验证逻辑在模拟时回滚。

回执持续返回 null — 操作仍在内存池中。每 3–5 秒轮询一次。如果超过 1 分钟仍为 null(GateChain 出块约 5 秒),操作可能因费用过低被丢弃。请更新 Gas 值后重新提交。

Nonce 冲突 — 每个 sender 在内存池中每个 nonce 同时只能有一个待确认的 UserOperation。要替换卡住的操作,请以相同 nonce 重新提交,并将 maxPriorityFeePerGas 提高至少 10%。

混淆 Gate Chain 与 Gate Layer — Gate Chain(Chain ID 86)和 Gate Layer(Chain ID 10088)是独立的网络。UserOperation 哈希包含 chainId,因此两条链之间的签名不可互换。请确认 SDK 配置使用了目标网络正确的链定义和 Bundler 端点。

最后更新于2026/03/20