Разработка смарт-контрактного кошелька (Account Abstraction)
Account Abstraction — это архитектурный сдвиг, который превращает кошелёк из пассивного хранилища ключей в программируемый агент. EIP-4337 стандартизировал этот подход без изменений на уровне протокола Ethereum: вся логика живёт в смарт-контрактах, а специализированная мемпул-инфраструктура (Bundlers и Paymasters) обрабатывает UserOperation объекты вместо обычных транзакций. Если вы строите продукт, где пользователь не должен думать о gas, seed phrase и approve — без AA не обойтись.
Архитектура EIP-4337: как это работает на самом деле
Компоненты системы
Классическая EOA-транзакция идёт напрямую в mempool и выполняется нодой. В AA-системе цепочка другая:
-
UserOperation — псевдотранзакция, подписанная пользователем. Содержит
callData,sender(адрес смарт-кошелька),signature, лимиты gas и параметры Paymaster. - Bundler — offchain агент, который собирает UserOperations из альтернативного mempool, упаковывает их в одну on-chain транзакцию и вызывает EntryPoint. Существующие реализации: Stackup, Pimlico, Alchemy Rundler (написан на Rust, на порядок быстрее референсной реализации).
- EntryPoint — singleton контракт (0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 на большинстве EVM сетей), развёрнутый командой ERC-4337. Именно он верифицирует и исполняет пачку операций. Нельзя деплоить свой EntryPoint — экосистема завязана на этот адрес.
-
Account Contract — сам смарт-кошелёк пользователя. Должен реализовывать интерфейс
IAccountс методомvalidateUserOp. Сюда идёт вся кастомная логика. - Paymaster — опциональный контракт, который платит gas за пользователя или принимает оплату в ERC-20 токенах вместо ETH.
Жизненный цикл UserOperation
User → sign UserOp → send to Bundler RPC
Bundler → simulate via eth_estimateUserOperationGas → validate signature + paymaster
Bundler → batch multiple UserOps → call EntryPoint.handleOps()
EntryPoint → validateUserOp() на каждом Account Contract
EntryPoint → Paymaster.validatePaymasterUserOp()
EntryPoint → execute callData
EntryPoint → postOp() на Paymaster (для учёта gas)
Важный момент: simulation и execution разделены. Bundler симулирует через eth_callStateOverride и реджектит операции, которые могут зафейлиться on-chain. Это защищает Bundler от потери ETH на failed transactions.
Реализация Account Contract
Базовая структура
За основу берём SimpleAccount от eth-infinitism или SafeAccount от Safe (бывший Gnosis Safe). Для production — Safe v1.4.1 с 4337 модулем, потому что он battle-tested с $100B+ TVL.
contract SmartWallet is IAccount, Initializable, UUPSUpgradeable {
address public owner;
IEntryPoint private immutable _entryPoint;
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external override returns (uint256 validationData) {
_requireFromEntryPoint();
validationData = _validateSignature(userOp, userOpHash);
_validateNonce(userOp.nonce);
_payPrefund(missingAccountFunds);
}
function _validateSignature(
UserOperation calldata userOp,
bytes32 userOpHash
) internal view returns (uint256) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
if (owner != hash.recover(userOp.signature))
return SIG_VALIDATION_FAILED;
return 0; // SIG_VALIDATION_SUCCESS
}
}
validationData кодирует три вещи: результат валидации (0 = успех, 1 = провал), validAfter и validUntil timestamps. Это позволяет делать time-bounded операции прямо в подписи.
Паттерны расширенной логики
Multisig в одном кошельке. Вместо owner хранится пороговое значение и список авторизованных подписантов. validateUserOp проверяет, что signatures содержит достаточное количество корректных подписей.
Session keys. Ограниченный ключ (например, сгенерированный браузером без seed phrase exposure), которому разрешены операции только в рамках конкретного контракта, лимита суммы и временного окна:
struct SessionKey {
address key;
address allowedContract;
uint256 spendingLimit;
uint48 validUntil;
bool enabled;
}
mapping(address => SessionKey) public sessionKeys;
Это фундамент для "gasless gaming" — пользователь один раз подписывает сессию на игровой контракт, дальше игра делает транзакции от его имени.
Social recovery. Guardians — доверенные адреса, которые могут сменить owner через timelock (обычно 72 часа). Реализация Argent — хороший референс: threshold из M-of-N guardians, cancellation в течение timelock window если owner онлайн.
Paymaster: газлесс и ERC-20 оплата
Sponsoring Paymaster
contract SponsoringPaymaster is IPaymaster {
mapping(address => bool) public whitelistedContracts;
function validatePaymasterUserOp(
UserOperation calldata userOp,
bytes32,
uint256 maxCost
) external returns (bytes memory context, uint256 validationData) {
// Спонсируем только вызовы whitelisted контрактов
address target = address(bytes20(userOp.callData[16:36]));
require(whitelistedContracts[target], "Not whitelisted");
require(deposit() >= maxCost, "Insufficient deposit");
return (abi.encode(userOp.sender), 0);
}
}
Paymaster должен держать депозит в EntryPoint. Механизм staking предотвращает DoS: Paymaster без stake может спонсировать максимум одну операцию за бандл.
ERC-20 Paymaster
Принимает любой ERC-20 как оплату gas. Оракул нужен для конвертации: Chainlink price feed или пул Uniswap V3 TWAP. Схема работы: до выполнения блокируем maxCost * exchangeRate токенов, после — списываем фактический cost через postOp.
Готовые решения: Pimlico ERC-20 Paymaster (open source), Stackup Paymaster SDK.
Factory и counterfactual деплой
Одно из ключевых свойств AA — кошелёк существует как адрес ещё до деплоя. CREATE2 с детерминированным salt (обычно хэш от owner address) даёт предсказуемый адрес. Пользователь получает адрес кошелька до первой транзакции, может принять средства — кошелёк деплоится автоматически при первом использовании.
contract WalletFactory {
function getAddress(address owner, uint256 salt) public view returns (address) {
return Create2.computeAddress(
bytes32(salt),
keccak256(abi.encodePacked(
type(ERC1967Proxy).creationCode,
abi.encode(address(implementation), initData(owner))
))
);
}
function createAccount(address owner, uint256 salt) external returns (SmartWallet) {
address addr = getAddress(owner, salt);
if (addr.code.length > 0) return SmartWallet(payable(addr)); // уже задеплоен
return SmartWallet(payable(new ERC1967Proxy{salt: bytes32(salt)}(
address(implementation), initData(owner)
)));
}
}
Фронтенд интеграция
Viem + permissionless.js — наиболее актуальный стек (2024–2025). permissionless построен на Viem и предоставляет абстракции для работы с Bundler и Paymaster RPC:
import { createSmartAccountClient } from "permissionless";
import { signerToSimpleSmartAccount } from "permissionless/accounts";
import { createPimlicoBundlerClient } from "permissionless/clients/pimlico";
const smartAccount = await signerToSimpleSmartAccount(publicClient, {
signer: walletClient,
factoryAddress: FACTORY_ADDRESS,
entryPoint: ENTRY_POINT_ADDRESS,
});
const smartAccountClient = createSmartAccountClient({
account: smartAccount,
chain: optimism,
bundlerTransport: http(BUNDLER_RPC_URL),
middleware: {
sponsorUserOperation: paymasterClient.sponsorUserOperation,
},
});
// Отправка транзакции — идентично обычному кошельку для пользователя
const txHash = await smartAccountClient.sendTransaction({
to: contractAddress,
data: encodeFunctionData({ abi, functionName: "doSomething" }),
});
ZeroDev SDK — альтернатива с более высоким уровнем абстракции, встроенными session keys и Kernel account (популярный Account Contract с plugin системой).
Альтернативы EIP-4337
zkSync Native AA — на zkSync Era AA встроена в протокол, не нужен отдельный EntryPoint. Каждый аккаунт может быть смарт-контрактом из коробки. Более эффективно по gas, но привязка к zkSync.
EIP-7702 (Prague/Electra) — предстоящий хардфорк Ethereum. Позволяет EOA временно делегировать исполнение смарт-контракту через специальный тип транзакции. Не заменяет 4337 полностью, но закрывает часть use cases проще.
Этапы разработки и оценки
| Компонент | Сложность | Срок |
|---|---|---|
| Базовый Account Contract (single owner) | Средняя | 1–2 нед |
| Factory + counterfactual deploy | Низкая | 3–5 дней |
| Sponsoring Paymaster | Средняя | 1 нед |
| ERC-20 Paymaster + оракул | Высокая | 1–2 нед |
| Session keys | Высокая | 1–2 нед |
| Social recovery | Высокая | 1–2 нед |
| Frontend SDK интеграция | Средняя | 1 нед |
| Аудит + исправления | — | 3–6 нед |
Минимальный production-ready кошелёк (single owner + sponsoring paymaster + фронтенд) — 4–6 недель разработки. Полнофункциональный продукт с social recovery, session keys и мультисетью — 3–5 месяцев.
Ключевой момент при выборе подрядчика: реализация validateUserOp должна быть аудирована. Ошибка в этой функции — прямая потеря средств пользователей. Экономия на аудите здесь = сознательный риск.







