EIPs

EIP-4337 — Account abstraction via entry-point

eips.ethereum.org/EIPS/eip-4337

The “smart account” standard. Replaces an EOA’s nonce + signature + tx_type flow with a UserOperation object that gets routed through a singleton EntryPoint contract. Bundlers aggregate user-operations into blocks; paymasters can sponsor gas.

import {
  ENTRY_POINT_V07_ADDRESS,
  eth_sendUserOperation,
  type UserOperation,
} from "@ethernauta/eip/4337";
import { create_writer, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const writer = create_writer([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

declare const user_op: UserOperation;

const hash = await eth_sendUserOperation({
  op: user_op,
  entryPoint: ENTRY_POINT_V07_ADDRESS,
})(writer({ chain_id: CHAIN_ID }));
void hash;

Surface

Entry points

ExportValue
ENTRY_POINT_V06_ADDRESS0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
ENTRY_POINT_V07_ADDRESS0x0000000071727De22E5E9d8BAf0edAc6f37da032

RPC methods

MethodShapePurpose
eth_estimateUserOperationGasSignable<EstimateUserOperationGasResult>Gas estimation for a UserOp.
eth_sendUserOperationSignable<Hash32>Submit a UserOp to a bundler.
eth_getUserOperationByHashReadable<UserOperationByHash>Look up a submitted UserOp.
eth_getUserOperationReceiptReadable<UserOperationReceipt>Receipt for a UserOp.
eth_supportedEntryPointsReadable<Address[]>Which entry-point addresses the node supports.

Packing

UserOperations have two on-the-wire shapes: the v0.6 form (separate fields) and the v0.7 form (packed into bytes32 fields). The packers convert between them:

ExportPurpose
pack_user_operationFull v0.6 → v0.7 packing.
pack_account_gas_limits(uint128, uint128)bytes32.
pack_gas_feesPriority fee + max fee → bytes32.
pack_init_codeFactory + factoryData → bytes.
pack_paymaster_and_dataPaymaster + gas limits + data.
unpack_uint128_pairReverse the gas-pair packing.

Signing

import { parse } from "valibot";
import type { Address, Uint } from "@ethernauta/core";
import { AddressSchema, UintSchema } from "@ethernauta/core";
import { sign_user_op, type UserOperation } from "@ethernauta/eip/4337";
import type { Provider } from "@ethernauta/eip/1193";
import { create_provider, encode_chain_id } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
declare const provider: Provider; // see /eips/6963 for discovery
const { signer } = create_provider(provider);

declare const op: UserOperation;
const owner: Address = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const entryPoint: Address = parse(AddressSchema, "0x0000000071727De22E5E9d8BAf0edAc6f37da032");
const chainId: Uint = parse(UintSchema, "0x1");

const signature = await sign_user_op({ op, owner, entryPoint, chainId })(
  signer({ chain_id: CHAIN_ID }),
);
void signature;

sign_user_op computes the UserOp hash and signs it. Used in test fixtures and off-wallet flows; the wallet does the same composition internally when serving an eth_sendTransaction-equivalent for smart accounts.

Types and schemas

UserOperation, PackedUserOperation, UserOperationByHash, UserOperationReceipt, EstimateUserOperationGasResult — all with matching Valibot schemas (*Schema).

A note on scope

EIP-4337 as written requires off-chain infrastructure (bundlers, paymasters). The library ships the protocol shapes — packing, hashing, signing, the RPC method bindings — but does not stand up a hosted bundler or paymaster. That keeps M4 (no hosted infrastructure) intact.

A dapp using 4337 today still needs a bundler endpoint somewhere (its own, a third-party service, or a chain whose node speaks the methods natively). The library is bundler-agnostic.

See also