į , a smart account was deployed and the first UserOperation successfully executed through the EntryPoint. At that point, everything worked — but a critical part of the system stayed mostly invisible: the bundler. 1 dalis Bundlers are the bridge between account abstraction and the Ethereum execution layer. They take UserOperations from a separate mempool, pay gas costs upfront, and get reimbursed through the protocol. Understanding how they work—the validation rules, reputation system, and economic incentives—is essential for debugging issues and building reliable applications. The UserOperation Lifecycle Jis apima viską, ko reikia norint atlikti veiksmą išmaniosios sąskaitos vardu - autorizaciją, dujų apribojimus, vykdymo skambučių duomenis ir neprivalomą "paymaster" logiką. In EntryPoint v0.8, UserOperations are handled on-chain in a packed, gas-optimized format. When working with SDKs like permissionless.js, they are represented as an explicit, unpacked structure: type UserOperation = { sender: Address nonce: bigint factory?: Address // Account factory (for first-time deployment) factoryData?: Hex // Factory calldata callData: Hex callGasLimit: bigint verificationGasLimit: bigint preVerificationGas: bigint maxFeePerGas: bigint maxPriorityFeePerGas: bigint paymaster?: Address paymasterVerificationGasLimit?: bigint paymasterPostOpGasLimit?: bigint paymasterData?: Hex signature: Hex } grandinėje „EntryPoint“ naudoja supakuotą dujų efektyvumo formatą (sujungia tokius laukus kaip SDK automatiškai tvarko šią pakuotę – dėl to nereikia jaudintis. accountGasLimits = verificationGasLimit | callGasLimit The lifecycle of a UserOperation looks like this: Kūrimas: vartotojas sukuria UserOp su savo išmaniojo paskyros SDK (pavyzdžiui, permissionless.js) Pasirašymas: Vartotojas pasirašo UserOp hash, įrodydamas, kad jis autorizavo veiksmą : UserOp is sent to a bundler via Submission eth_sendUserOperation Patvirtinimas: Bundler imituoja UserOp patikrinti, ar jis bus sėkmingas : If valid, the UserOp enters the bundler's mempool Mempool Bundling: Bundler paketas kelis UserOps į vieną "HandOps" skambutį Vykdymas: „EntryPoint“ sutartis patvirtina kiekvieną „UserOp“ grandinėje, tada ją vykdo Mokėjimas: „EntryPoint“ renka dujų išlaidas iš kiekvienos sąskaitos (arba jos mokėtojo). Pagrindinė įžvalga yra ta, kad patvirtinimas vyksta du kartus: vieną kartą išjungus paketą (sprendžiant, ar priimti UserOp), ir vieną kartą ant grandinės EntryPoint (prieš iš tikrųjų vykdant). This asymmetry is why bundlers are intentionally strict. Every validation rule exists to eliminate a class of attack where a UserOp passes simulation but fails on-chain. Validacija: Kodėl Bundlers yra paranojiški Bundlers pay gas costs upfront. If a UserOperation fails on-chain after being included in a bundle, the loss is theirs. No refunds, no retries. That single fact defines the entire bundler threat model. Between simulation and inclusion, Ethereum state is not static. Block parameters shift, balances change, and adversarial transactions can land in between. A carefully crafted UserOperation can pass off-chain simulation and still fail during on-chain validation — turning the bundler into a gas sink. ERC-4337 reaguoja griežtai apriboti, ką patvirtinimo kodas leidžiama daryti. The EntryPoint enforces a strict separation of concerns: : Paskyros validateUserOp funkcija veikia, kad patvirtintų parašą ir įgaliotų operaciją.Šis etapas turi griežtus apribojimus, kokius opcodes ir saugyklą kodas gali pasiekti. Validation Phase : The account's execute function runs the actual operation. No restrictions here—full EVM capabilities. Execution Phase Banned Opcodes Kai kurie opcodai yra uždrausti patvirtinimo metu, nes jų reikšmės gali pasikeisti tarp modeliavimo ir vykdymo: , , , : Block-dependent values. An account could check if (block.timestamp > deadline) revert, pass simulation, then fail when the bundle lands in a later block. TIMESTAMP NUMBER COINBASE PREVRANDAO BLOCKHASH: grąžina skirtingas reikšmes skirtingiems blokams. GASPRICE, BASEFEE: pokyčiai, pagrįsti tinklo sąlygomis. , : Only allowed from staked entities (see Staking below) per ERC-7562 [OP-080]. BALANCE SELFBALANCE , : Could vary between simulation environment and actual execution. GASLIMIT ORIGIN BLOBHASH, BLOBBASEFEE: EIP-4844 blob susiję opcodes, kurie skiriasi pagal bloką. , : Destructive opcodes not allowed during validation. SELFDESTRUCT INVALID GAZAS: Leidžiama tik tuomet, kai iš karto po to yra nurodymas *CALL - CALL, DELEGATECALL, STATICCALL arba CALLCODE (dėl dujų perdavimo kitoms sutartims). CREATE: paprastai draudžiama, nes ji sukuria sutartis neprognozuojamuose adresuose. Tačiau CREATE leidžiama siuntėjo sutarčiai, kai naudojama neprisijungusi gamykla. CREATE2 leidžiama tiksliai vieną kartą siuntėjo sutarties diegimo metu. Here's what happens when your account uses a banned opcode: // This validateUserOp will be rejected by bundlers function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData) { // BANNED: block.timestamp can change require(block.timestamp < deadline, "Expired"); // ... rest of validation } The bundler runs a trace during simulation (using with an ERC-7562 compliant tracer) and rejects any UserOp whose validation touches banned opcodes. Modern bundlers may use either a JavaScript tracer or the native Go implementation introduced with EntryPoint v0.8. The RPC response will indicate which opcode caused the rejection. debug_traceCall Prieigos prie saugyklos taisyklės Be opcode, bundlers apriboti, kurios saugyklos lizdų patvirtinimo kodą galima skaityti ir rašyti. taisyklės (nuo ERC-7562) yra maždaug: Galima naudotis tik: Unstaked entities Jų pačių saugykla (sąskaitos saugykla) saugyklos lizdai, kurie yra „susiję“ su sąskaitos adresu A, apibrėžti kaip: lizdo vertė yra lygi A, ARBA buvo apskaičiuojama kaip keccak256(Aannoo x) + n, kur x yra bytes32 ir n yra nuo 0 iki 128 In practice, this means your account can read/write to mappings where the account address is the key. For example, an ERC-20 token stores balances in a mapping like Tikrasis saugojimo lizdas yra . If your account address is the key, that slot is "associated" with you. The offset (0-128) allows access to struct members stored after the mapping entry—useful when the mapping value is a struct. mapping(address => uint256) balances keccak256(address || slot_of_mapping) +n (sąskaitos, mokėjimo administratoriai ar gamyklos, turinčios indėlį į „EntryPoint“) gauna daugiau laisvės: Staked entities Galima pasiekti bet kurį lizdą savo saugykloje (STO-031) Can access the associated storage of any entity in the UserOp (STO-032) Can have read-only access to storage in non-entity contracts (STO-033) See the full storage access rules in . Kėdainiai 7562 Why do these rules exist? Consider two UserOperations that read from the same storage slot during validation. If the first operation mutates that slot, the second operation’s validation assumptions may no longer hold. By restricting storage access during validation, bundlers can safely include multiple UserOperations in the same bundle without risking cross-operation interference or non-deterministic failures. The Reputation System Net su patvirtinimo taisyklėmis subjektai gali elgtis blogai. paskyra gali nuosekliai pateikti UserOps, kurie praeina simuliaciją, bet nepavyksta grandinėje dėl subtilių būsenos pokyčių. mokėjimo administratorius gali patvirtinti UserOps simuliacijos metu, bet atsisakyti mokėjimo grandinėje. Bundlers track these entities with a . For each entity (account address, paymaster address, factory address), the bundler tracks: reputation system : How many UserOps involving this entity has the bundler seen opsSeen : How many of those actually got included on-chain opsIncluded Reputacijos statusas nustatomas pagal slėgio ribas (apibrėžtas ): Kėdainiai 7562 maxSeen = opsSeen / MIN_INCLUSION_RATE_DENOMINATOR (10 for bundlers) status = BANNED if maxSeen > opsIncluded + BAN_SLACK (50) status = THROTTLED if maxSeen > opsIncluded + THROTTLING_SLACK (10) status = OK otherwise The "slack" values are tolerance buffers that prevent false positives from normal operational variance. means an entity can have up to 10 more expected-inclusions than actual-inclusions before being throttled. provides an even larger buffer before permanent banning. This design acknowledges that some UserOps legitimately fail (network conditions, race conditions) without indicating malicious behavior. THROTTLING_SLACK = 10 BAN_SLACK = 50 Kai subjektas yra , the bundler limits how many UserOps from that entity can be in the mempool. When , visi UserOps, susiję su tuo subjektu, nedelsiant atmetami. throttled banned Streikuoja Entities can improve their standing by staking ETH in the EntryPoint contract: entryPoint.addStake{ value: 1 ether }(unstakeDelaySec); Įmoka nėra sumažinta – ji tiesiog užrakinta, tačiau tai rodo įsipareigojimą ir dotacijas: Relaxed storage access rules (as described above) Higher mempool limits Didesnės reputacijos ribos Mokesčių administratoriams ir gamykloms, ypač, statymas yra beveik privalomas gamybos naudojimui. Be jo, vienas nesėkmingas UserOp gali greitai gauti subjektą. (specifinė grandinė, paprastai 1 ETH arba lygiavertis) ir of 86400 seconds (1 day). The exact stake amount varies by chain and is defined in the mempool metadata—check the bundler documentation for your target network. MIN_STAKE_VALUE MIN_UNSTAKE_DELAY Gas Economics Bundlers are businesses. They pay gas costs to submit bundles and get reimbursed from the UserOps they include. The economics work like this: The Bundler's Perspective Revenue = Σ (each UserOp's payment to beneficiary) Cost = Gas used × Gas price paid for handleOps tx Profit = Revenue - Cost Kiekvienas UserOp moka pagal savo dujų naudojimą ir dujų kainas, kurias jis nurodė: Payment = (actualGasUsed + preVerificationGas) × min(maxFeePerGas, baseFee + maxPriorityFeePerGas) „Bundler“ nustato address in the Norėdami gauti šiuos mokėjimus. beneficiary handleOps Išankstinis patikrinimasPaaiškėjo The Lauko sritis apima išlaidas, kurių negalima tiesiogiai išmatuoti: preVerificationGas : 16 gas per non-zero byte, 4 gas per zero byte Calldata cost : Fixed costs per handleOps call that get amortized across UserOps Bundle overhead : On L2s like Optimism or Arbitrum, posting calldata to L1 has additional costs L2 data fees When estimating gas, bundlers calculate preVerificationGas based on the UserOp's size: // Simplified preVerificationGas calculation const calldataCost = userOpBytes.reduce((sum, byte) => sum + (byte === 0 ? 4n : 16n), 0n ); const overhead = 38000n; // ~21000 tx base + ~10000 bundle overhead + ~7000 per-op preVerificationGas = calldataCost + overhead + l2DataFee; Overhead values vary by bundler. For reference, Alto uses , , Visada naudokite rather than hardcoding. transactionGasStipend: 21000 fixedGasOverhead: 9830 perUserOp: 7260 eth_estimateUserOperationGas Nepanaudotos dujos baudžiamos Siekiant užkirsti kelią vartotojams pernelyg daug mokėti už dujas (kuris švaisto blokų erdvę), „EntryPoint“ skiria baudą už nepanaudotas vykdymo dujas. (for account execution) and (už „Paymaster“ po operacijų): callGasLimit paymasterPostOpGasLimit If unused gas in either field exceeds (40,000), the account pays 10% ( ) of the unused amount (does NOT apply to or ) PENALTY_GAS_THRESHOLD UNUSED_GAS_PENALTY_PERCENT verificationGasLimit preVerificationGas This discourages setting absurdly high execution limits "just to be safe" When you see gas estimation errors, check if your limits are reasonable. The bundler's endpoint provides sensible defaults. eth_estimateUserOperationGas Common Errors and Debugging When a UserOperation is rejected, the RPC error is the only signal you get. Bundlers return structured error codes that explain exactly why the operation failed — but only if you know how to read them. AA1x: Factory Errors Tai atsitinka, kai įdiegiate naują paskyrą per gamyklą. „EntryPoint v0.8“ and kaip atskiri laukai (EntryPoint juos supakuoja į Iš vidaus : factory factoryData initCode AA10: „siuntėjas jau sukonstruotas“ – siuntėjo adresas jau turi įdiegtą kodą. AA13: "initCode nepavyko arba OOG" - gamyklos "CreateAccount" skambutis nepavyko arba baigėsi. : "initCode must return sender" - The factory returned a different address than expected. AA14 : "initCode must create sender" - The factory call completed but didn't deploy code to the sender address. AA15 : Check that your factory's function returns the expected address. Verify the factory is deployed and funded. Debugging createAccount AA2x: Account Validation Errors The most common category: : "account not deployed" - The sender address has no code and no was provided. AA20 initCode AA21: „nesumokėjo išankstinio finansavimo“ - sąskaitoje nėra pakankamai ETH, kad būtų padengtos didžiausios galimos dujų išlaidos. AA22: „Išnyko arba nepasibaigė“ - UserOp turi galiojantį laikrodį, kuris praėjo, arba galiojantį laikrodį, kuris dar neatvyko. AA23: "grąžinta" - paskyros validateUserOp funkcija grąžinta. patikrinkite savo parašo patvirtinimo logiką. : "signature error" - The returned validation data indicates an invalid signature. AA24 : "invalid account nonce" - The nonce doesn't match. The nonce in ERC-4337 is a 256-bit value with two parts: . The (upper 192 bits) identifies the "lane"—you can have multiple parallel UserOps with different keys. The (lower 64 bits) must increment sequentially within each lane. Common causes: AA25 nonce = (key << 64) | sequence key sequence Reusing a nonce that was already included Naudojant neteisingą nonce raktą Kitas UserOp su tuo pačiu siuntėju laukia mempool : "over verificationGasLimit" - Account validation used more gas than allocated. Increase . AA26 verificationGasLimit AA3x: Paymaster Errors : "paymaster not deployed" - The paymaster address has no code deployed. AA30 : "paymaster deposit too low" - The paymaster's deposit in the EntryPoint can't cover the gas cost. Top it up: AA31 apyvartinių taršos leidimų (PayMasterAddress) : "paymaster expired or not due" - Similar to AA22, but for the paymaster's validation data. AA32 AA33: „paymaster reverted“ - „paymaster“ patvirtinta „PaymasterUserOp“ funkcija grįžta. : "paymaster signature error" - Bad signature in the paymaster data. AA34 : "over paymasterVerificationGasLimit" - Paymaster validation used more gas than allocated. Increase . AA36 paymasterVerificationGasLimit Working with Bundlers RPC metodai Kiekvienas ERC-4337 maišytuvas įgyvendina šiuos standartinius metodus: : Submit a UserOp for inclusion eth_sendUserOperation const userOpHash = await bundler.request({ method: 'eth_sendUserOperation', params: [userOp, entryPointAddress] }); Gaukite dujų limitų skaičiavimus eth_estimateUserOperationGas const gasEstimate = await bundler.request({ method: 'eth_estimateUserOperationGas', params: [userOp, entryPointAddress] }); // Returns: { preVerificationGas, verificationGasLimit, callGasLimit, ... } : Look up a UserOp by its hash eth_getUserOperationByHash const userOp = await bundler.request({ method: 'eth_getUserOperationByHash', params: [userOpHash] }); : Get the receipt after inclusion eth_getUserOperationReceipt const receipt = await bundler.request({ method: 'eth_getUserOperationReceipt', params: [userOpHash] }); // Returns: { success, actualGasUsed, receipt: { transactionHash, ... } } : Discover which EntryPoint versions the bundler supports eth_supportedEntryPoints const entryPoints = await bundler.request({ method: 'eth_supportedEntryPoints', params: [] }); // Returns: ['0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108'] Pasidalintas Mempolis Iš pradžių kiekvienas paketėlis išlaikė savo privatų mempool. : A single bundler could refuse to include certain UserOps Censorship risk Fragmentacija: vartotojai turėjo žinoti, kuriuos paketus pateikti Vienkartiniai nesėkmės taškai: jei jūsų paketėlis nukrito, jūsų UserOps buvo įstrigę Sprendimas yra , a P2P network where bundlers gossip UserOps to each other. It works similarly to how Ethereum nodes gossip transactions: ERC-4337 shared mempool Vartotojas siunčia UserOp bet kuriam dalyvaujančiam paketui Bundler patvirtina ir prideda prie vietinio mempool „Bundler“ transliacijos sujungtiems bendraamžiams Bet kuris tinklo paketėlis gali apimti UserOp. The protocol uses libp2p for networking. Bundlers advertise which mempools they support (identified by IPFS CIDs that reference mempool metadata files), and only propagate UserOps that pass validation. For example, a mempool metadata file looks like: chainId: '1' entryPointContract: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' description: Canonical ERC-4337 mempool for Ethereum Mainnet minimumStake: '1000000000000000000' Šio failo IPFS CID tampa mempool identifikatoriumi, naudojamu P2P temų pavadinimuose. mempool metaduomenys apibrėžia patvirtinimo taisykles: kurie opcodes yra uždrausti, prieigos prie saugyklos modeliai, dujų apribojimai ir reputacijos slenksčiai. Išplėstinės temos Aggregators Parašų tikrinimas yra brangus grandinėje. „Ecrecover“ išankstinis sudarymas kainuoja 3 000 dujų skambučiui, tačiau išmaniųjų sąskaitų parašų tikrinimas paprastai kainuoja daugiau dėl papildomos patvirtinimo logikos – dažnai 6 000–10 000 dujų. What problem do aggregators solve? Vietoj to, kad kiekviena sąskaita patikrintų savo parašą, sąskaitos gali deleguoti į agregatoriaus sutartį. How it works: Paskyros validateUserOp grąžina agregatoriaus adresą savo patvirtinimo duomenimis Bundler groups all UserOps using the same aggregator Bundler calls once for the group aggregator.validateSignatures(userOps, aggregatedSignature) Jei patvirtinimas praeina, visos toje grupėje esančios UserOps laikomos galiojančiomis : The return value from packs three pieces of information into a single 256-bit value: The validationData encoding validateUserOp validationData = uint160(aggregator) | // bits 0-159: aggregator address (uint256(validUntil) << 160) | // bits 160-207: expiration timestamp (uint256(validAfter) << 208) // bits 208-255: earliest valid time (bits 0-159): Address of the aggregator contract, or special values: 0 = signature valid, 1 = signature invalid aggregator validUntil (bitai 160-207): Laiko žymeklis, po kurio šis UserOp pasibaigia (0 = nėra galiojimo pabaigos) (bits 208-255): Timestamp before which this UserOp is not valid (0 = immediately valid) validAfter This encoding lets accounts specify both signature verification delegation and time-bounded validity in a single return value. Paymasters Paymasters abstract gas payment from users. Instead of the account paying for gas, a paymaster can: : Pay on behalf of users (gasless UX) Sponsor transactions Priimkite ERC-20 žetonus: leiskite vartotojams mokėti stablecoins arba kitus žetonus : Rate limiting, subscription models, etc. Implement custom logic The paymaster's validation flow runs during the validation phase: function validatePaymasterUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) external returns (bytes memory context, uint256 validationData); The returned here is passed to po to, kai vykdymas baigiasi, leidžiant mokėtojui atlikti galutinę apskaitą (pvz., Mokėti ERC-20 žetoną): context postOp function postOp( PostOpMode mode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas ) external; Paymasters should be staked for production use. Staking provides relaxed storage access rules and better reputation—unstaked paymasters face strict limitations and can be quickly throttled by bundlers. While unstaked paymasters technically function with basic operations, staking is practically required for any serious paymaster implementation. can Testing Locally Šiame skyriuje daroma prielaida, kad „Anvil“ veikia naudojant „EntryPoint v0.8“. , Pimlico's TypeScript bundler, and ERC-4337 sąveikos biblioteka. Alto Nepriklausomybė.js SimpleAccountFactory In Part 1 we built a minimal smart account. But how do users deploy it? They can't send a regular transaction—they don't have ETH for gas yet. ERC-4337 solves this with factory contracts. už Atsižvelgiant į tai, referencinis įgyvendinimas apima . Deploy it alongside the EntryPoint before running the examples below. SimpleAccount Paprastosios gamyklos Account Deployment via UserOp When the EntryPoint receives a UserOp with factory and factoryData fields: Checks if has code—if yes, skip deployment sender Skambina factory.createAccount(savininkas, druska) per factoryData Patikrinkite, ar įdiegtas adresas atitinka siuntėją Continues with validation on the newly-deployed account Bėgimas aukštai alto \ --rpc-url http://localhost:8545 \ --entrypoints 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 \ --executor-private-keys 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \ --utility-private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --safe-mode false \ --api-version v1,v2 \ --bundle-mode auto Pagrindinės vėliavos: : Key for submitting bundles (must have ETH) --executor-private-keys --safe-mode false: Anvil trūksta JavaScript sekimo, kad galėtumėte visiškai patvirtinti ERC-7562 --api-version v1,v2: priimti abu UserOp formatus (v1 už 0.6, v2 už 0.7/0.8) Sending UserOperations with permissionless.js Įdiegti priklausomybę: npm install viem permissionless Step 1: Set up clients Mums reikia trijų klientų: vieno skaitymo grandinės būsenai, vieno paketėlių specifinių RPC ir vieno išmaniųjų sąskaitų savininkui. import { http, createPublicClient, createWalletClient, parseEther } from "viem" import { privateKeyToAccount } from "viem/accounts" import { foundry } from "viem/chains" import { toSimpleSmartAccount } from "permissionless/accounts" import { createSmartAccountClient } from "permissionless/clients" import { createPimlicoClient } from "permissionless/clients/pimlico" const ENTRYPOINT = "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108" const publicClient = createPublicClient({ chain: foundry, transport: http("http://localhost:8545") }) const pimlicoClient = createPimlicoClient({ chain: foundry, transport: http("http://localhost:4337"), entryPoint: { address: ENTRYPOINT, version: "0.8" } }) const owner = privateKeyToAccount(process.env.PRIVATE_KEY) The connects to Alto's RPC and provides gas estimation via . pimlicoClient pimlico_getUserOperationGasPrice Step 2: Create the smart account instance const simpleAccount = await toSimpleSmartAccount({ client: publicClient, owner, entryPoint: { address: ENTRYPOINT, version: "0.8" } }) const accountAddress = await simpleAccount.getAddress() console.log("Account:", accountAddress) This computes the counterfactual address using the factory's Paskyra dar neegzistuoja, bet mes tiksliai žinome, kur ji bus įdiegta. getAddress Step 3: Fund the account The smart account needs ETH to pay for gas (or use a paymaster). We can send ETH to the counterfactual address: const walletClient = createWalletClient({ account: owner, chain: foundry, transport: http("http://localhost:8545") }) await walletClient.sendTransaction({ to: accountAddress, value: parseEther("1") }) The ETH sits at that address. When the account is deployed, it can access those funds immediately. Step 4: Create the smart account client const smartAccountClient = createSmartAccountClient({ client: publicClient, account: simpleAccount, bundlerTransport: http("http://localhost:4337"), userOperation: { estimateFeesPerGas: async () => (await pimlicoClient.getUserOperationGasPrice()).fast } }) The tvarko UserOp statybą, nonce valdymą, dujų vertinimą ir pasirašymą. „Callback“ gauna dabartines dujų kainas iš paketų. smartAccountClient estimateFeesPerGas Step 5: Send a UserOperation const hash = await smartAccountClient.sendUserOperation({ calls: [{ to: "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", value: parseEther("0.01"), data: "0x" }] }) const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash }) console.log("Success:", receipt.success) For the first UserOp, the SDK automatically includes and „EntryPoint“ diegia paskyrą, tada atlieka pervedimą – visa tai atliekant vieną operaciją. factory factoryData What We've Learned Bundlers are the execution layer of ERC-4337. They are what turns Account Abstraction from a specification into a production-ready mechanism. Suprasti jų apribojimus - patvirtinimo taisykles, dujų ekonomiką ir reputacijos mechaniką - yra labai svarbu kuriant patikimas protingas sąskaitas. ERC-4337 shifts complexity away from the protocol and into infrastructure. The sooner developers start thinking in terms of bundlers rather than transactions, the more resilient their systems will be in production.