Skip to main content

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

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

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


1. 网络参数

Gate Layer 主网

参数
Chain ID10088
RPC(HTTP)https://gatelayer-mainnet.gatenode.cc
RPC(WSS)wss://gatelayer-ws-mainnet.gatenode.cc
Bundlerhttps://gatelayer-bundler.gatenode.cc
EntryPoint(0.8)0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108
区块浏览器https://www.gatescan.org/gatelayer
Gas 代币GT
AA 标准ERC-4337 v0.7

Gate Layer 测试网

参数
Chain ID10087
RPC(HTTP)https://gatelayer-testnet.gatenode.cc
RPC(WSS)wss://gatelayer-ws-testnet.gatenode.cc
Bundlerhttps://gatelayer-testnet-bundler.gatenode.cc
区块浏览器https://www.gatescan.org/gatelayer-testnet
水龙头https://www.gatechain.io/docs/GateLayer/GettingStarted/GetTestnetGT

2. 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。


3. EntryPoint 合约

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

curl -s "https://gatelayer-bundler.gatenode.cc" \
-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 地址。硬编码不同版本的地址会导致静默验证失败。


4. Gas 费用机制

Gate Layer 运行于 OP Stack。每笔交易需支付三项费用,均以 GT 计价:

totalFee = l2ExecutionFee + l1DataFee + operatorFee

L2 执行费 — 链上执行的标准 EIP-1559 费用:

l2ExecutionFee = gasUsed × (baseFee + priorityFee)

L1 数据费 — 通过 EIP-4844 blobs 将交易数据发布至 Gate Chain 的成本:

l1DataFee = estimatedCompressedSize × (baseFeeScalar × l1BaseFee × 16 + blobFeeScalar × l1BlobBaseFee) / 10¹²

当前 Gate Layer 参数:baseFeeScalar = 113016blobFeeScalar = 801949

Operator 费 — 当前为零(operatorFeeScalar = 0operatorFeeConstant = 0)。目前不收取额外的运营商费用。

对于 UserOperation,三项费用均须包含在 UserOperation 的 Gas 预算中。签名前请使用 eth_estimateUserOperationGaseth_maxPriorityFeePerGas 计算正确值。


5. UserOperation 结构(v0.7)

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

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

6. 集成流程

第 1 步 — 查询 EntryPoint

curl -s "https://gatelayer-bundler.gatenode.cc" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_supportedEntryPoints","params":[]}'

第 2 步 — 获取优先费

curl -s "https://gatelayer-mainnet.gatenode.cc" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"eth_maxPriorityFeePerGas","params":[]}'

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

第 3 步 — 估算 Gas

将所有 Gas 字段设为 0x0,并使用哑签名发送 UserOperation:

curl -s "https://gatelayer-bundler.gatenode.cc" \
-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>"
]
}'

关于占位签名(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
))
signature = owner.signMessage(userOpHash) // 需要 ETH 签名消息前缀

第 5 步 — 提交

curl -s "https://gatelayer-bundler.gatenode.cc" \
-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>",
"factory": "",
"factoryData": "0x",
"paymaster": "",
"paymasterVerificationGasLimit": "0x0",
"paymasterPostOpGasLimit": "0x0",
"paymasterData": "0x",
"signature": "<SIGNED_USER_OPERATION_HEX>"
},
"<ENTRY_POINT_ADDRESS>"
]
}'

返回示例:

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

第 6 步 — 轮询回执

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

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


7. 自付模式 vs. Paymaster 模式

默认情况下无需 Paymaster,智能账户从其 EntryPoint 存款中支付 Gas。

自付模式(无 Paymaster)

将所有 paymaster 字段留空或设为零:

"paymaster": "",
"paymasterVerificationGasLimit": "0x0",
"paymasterPostOpGasLimit": "0x0",
"paymasterData": "0x"

提交前,请确保智能账户在 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 后提供。


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

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

安装依赖

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

定义 Gate Layer 链

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://gatelayer-mainnet.gatenode.cc',
BUNDLER_URL = 'https://gatelayer-bundler.gatenode.cc',
ENTRY_POINT = '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
LIGHT_ACCOUNT_FACTORY = '0x13E9ed32155810FDbd067D4522C492D6f68E5944',
RECIPIENT_ADDRESS = '0xRECIPIENT_ADDRESS',
CHAIN_ID = '10088',
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);
});

9. EIP-7702 授权字段

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

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

字段结构:

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

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


10. 智能账户部署

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

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

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

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

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


11. RPC 方法参考

核心方法

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

数值格式

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


12. 错误代码参考

代码名称原因解决方案
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() 失败检查 paymaster 数据和策略条件
AA40超出验证 Gas 上限verificationGasLimit 过低重新运行 eth_estimateUserOperationGas
AA51预付款低于实际 Gas 成本maxFeePerGas 低于当前网络水平重新查询 eth_maxPriorityFeePerGas 并重建 UserOp

13. 故障排查

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

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

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

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


14. 快速参考

项目
主网 Bundlerhttps://gatelayer-bundler.gatenode.cc
主网 Chain ID10088
测试网 Chain ID10087
AA 标准ERC-4337 v0.7
EntryPoint v0.8通过 eth_supportedEntryPoints 查询
Gas 代币GT
数值格式所有值均带 0x 前缀的十六进制
核心流程eth_supportedEntryPoints → 构建 → eth_estimateUserOperationGas → 签名 → eth_sendUserOperationeth_getUserOperationReceipt

15. 参考资料

  • Gate Layer 端点 & API:https://www.gatechain.io/docs/GateLayer/Development/EndpointsAPI/
  • Gate Layer Gas 与费用机制:https://www.gatechain.io/docs/GateLayer/Concepts/GasAndFees/
  • Gate Layer 区块浏览器:https://www.gatescan.org/gatelayer
  • ERC-4337 规范:https://eips.ethereum.org/EIPS/eip-4337
  • permissionless.js:https://docs.pimlico.io/permissionless

最后更新于2026/03/20