ב , חשבונית חכמה הוגשה והפעולה הראשונה של UserOperation הושלמה בהצלחה באמצעות EntryPoint. בשלב זה, הכל עבד - אבל חלק קריטי של המערכת נשאר בעיקר בלתי נראה: החבילה. חלק 1 חבילות הם הגשר בין חיסול החשבון לבין שכבת ביצועי Ethereum.הן לוקחות UserOperations מ- mempool נפרד, משלמות את עלויות הגז מראש, וקבלות החזר דרך הפרוטוקול.הבנה של איך הם עובדים - הכללים של אימות, מערכת מוניטין, ועידוד כלכלי - הוא חיוני לבדיקת בעיות ובניית יישומים אמין. מעגל החיים של המשתמש UserOperation הוא היחידה שבה פועלים חבילות עבודה, והיא מכסה את כל מה שנדרש כדי לבצע פעולה בשם חשבון חכם – הסמכות, מגבלות גז, נתוני שיחות ביצוע והיגיון paymaster אופציונלי. ב- EntryPoint v0.8, UserOperations מתבצעים בשרשרת בפורמט משולב ונועד לאופטימיזציה גז. בעת עבודה עם SDKs כגון permissionless.js, הם מוצגים כבניין מפורש, לא משולב: 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 } בשרשרת, EntryPoint משתמש בפורמט משולב עבור יעילות גז (שלב של שדות כגון ה-SDK מתמודד עם החבילה הזו באופן אוטומטי – אין צורך לדאוג לכך. accountGasLimits = verificationGasLimit | callGasLimit מחזור החיים של פעולה משתמש נראה כך: יצירה: המשתמש בונה את UserOp עם ה-SDK של החשבון החכם שלו (כמו permissionless.js) חתימה: המשתמש חותם את הקישור UserOp, ומוכיח שהם אישרו את הפעולה משלוח: UserOp נשלח לחבילה באמצעות eth_sendUserOperation אימות: Bundler מדמיין את UserOp כדי לבדוק אם זה יצליח Mempool: אם תקף, UserOp נכנס mempool של החבילה Bundling: Bundler חבילות UserOps מרובות לשיחה אחת ביצוע: חוזה EntryPoint מאשר כל UserOp בשרשרת, ולאחר מכן מבצע אותם תשלום: EntryPoint מעבירה את עלויות הגז מכל חשבון (או ממנהל התשלומים שלהם). ההבנה העיקרית היא כי אימות מתרחש פעמיים: פעם מחוץ לשרשרת על ידי החבילה (להחליט אם לקבל את UserOp), פעם על שרשרת על ידי EntryPoint (לפני למעשה לבצע). כל חוק אימות קיים כדי לחסל כיתה של התקפה שבה UserOp עובר סימולציה אבל נכשל על שרשרת. תגיות: מדוע בונדרים הם פרנואידים בולרים משלמים עלויות גז מראש.אם עסקה משתמש נכשלת בשרשרת לאחר שהוקמה בחבילה, האובדן שייך להם. העובדה היחידה הזו מגדירה את המודל המקיף של האיום. בין סימולציה והכללה, מצב Ethereum אינו סטטי.פרמטרים בלוק משתנים, משקלים משתנים, ועסקאות יריבות יכולות להגיע ביניהם.UserOperation מעוצב בקפידה יכול לעבור את סימולציה שרשרת ועדיין להיכשל במהלך אימות בשרשרת - להפוך את החבילה לתוך מיכל גז. ERC-4337 מגיב על ידי הגבלה חדה של מה קוד אימות מותר לעשות. The EntryPoint enforces a strict separation of concerns: הפונקציה ValidateUserOp של החשבון פועלת כדי לאמת את החתימה ולאשר את הפעולה.בשלב זה יש הגבלות קפדניות על מה opcodes והאחסון הקוד יכול לגשת. Validation Phase : הפונקציה של החשבון מבצעת את הפעולה בפועל. אין הגבלות כאן – יכולות EVM מלאות. Execution Phase קודים אסורים קודים אופטיים מסוימים אסורים במהלך אימות מכיוון שהערכים שלהם עשויים להשתנות בין סימולציה לביצוע: TIMESTAMP, NUMBER, COINBASE, PREVRANDAO: ערכים תלויים בלוק. חשבון יכול לבדוק אם (block.timestamp > deadline) לחזור, לעבור סימולציה, ולאחר מכן להיכשל כאשר החבילה נחתה בלוק מאוחר יותר. BLOCKHASH: מחזיר ערכים שונים עבור בלוקים שונים. GASPRICE, BASEFEE: שינוי בהתבסס על תנאי הרשת. איזון, איזון עצמי: מותר רק על ידי ישויות המעורבות (ראה הימורים למטה) על ידי ERC-7562 [OP-080]. GASLIMIT, ORIGIN: יכול להשתנות בין סביבת הסימולציה לבין ביצוע בפועל. BLOBHASH, BLOBBASEFEE: EIP-4844 קודים קשורים blob אשר משתנים על ידי בלוק. SELFDESTRUCT, INVALID: אופקודים הרסניים אינם מורשים במהלך האימות. גז: מותר רק כאשר מיד בעקבות ההוראה *CALL – CALL, DELEGATECALL, STATICCALL, או CALLCODE (לשלוח גז לחברות אחרות). CREATE: בדרך כלל אסור כי זה יוצר חוזה בכתובות לא צפויות. עם זאת, CREATE מותר עבור החוזה של השולח כאשר משתמשים במפעל חסום. CREATE2 מותר בדיוק פעם אחת במהלך הפעלת החוזה של השולח. הנה מה שקורה כאשר החשבון שלך משתמש בקוד אסור: // 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 } החבילה מבצעת עקבות במהלך הסימולציה (באמצעות עם מעקב תואם ERC-7562) ומכחיש כל UserOp אשר אימותו נוגע opcodes אסורים.בנקים מודרניים עשויים להשתמש או מעקב JavaScript או יישום Go מקורי שהושק עם EntryPoint v0.8. debug_traceCall חוקי גישה לאחסון Beyond opcodes, bundlers restrict which storage slots validation code can read and write. The rules (from ERC-7562) are roughly: can only access: Unstaked entities Their own storage (the account's storage) Storage slots that are "associated" with the account address A, defined as: the slot value equals A, OR was calculated as where x is bytes32 and n is in range 0 to 128 keccak256(A || x) + n במציאות, זה אומר שהחשבון שלך יכול לקרוא/כתוב למפות שבהן כתובת החשבון היא המפתח. . The actual storage slot is אם כתובת החשבון שלך היא המפתח, החריץ הזה "קשור" אליך. 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 (חשבונות, מנהלי תשלומים או מפעלים שיש להם השקעה ב- EntryPoint) מקבלים יותר חופש: Staked entities Can access any slot in their own storage (STO-031) יכול לגשת לאחסון הקשור של כל ישות ב- UserOp (STO-032) Can have read-only access to storage in non-entity contracts (STO-033) See the full storage access rules in . ERC-7562 למה הכללים האלה קיימים?קח בחשבון שתי פעולות משתמש שמקבלות קריאה מאותו חריץ אחסון במהלך אימות.אם הפעולה הראשונה מוטציה את החריץ הזה, ייתכן שהנחות האימות של הפעולה השנייה לא יימשכו עוד. על ידי הגבלת גישה לאחסון במהלך אימות, חבילות יכולות לכלול בבטחה מספר פעולות משתמש באותו חבילה מבלי לסכן הפרעות בין פעולות או כישלונות לא-דטרמיניסטיות. מערכת המוניטין אפילו עם כללים של אימות, ישויות עשויות להתנהג בצורה גרועה.חשבון עשוי לעתים קרובות להגיש UserOps אשר עברו סימולציה אבל נכשלו בשרשרת עקב שינויים רגישים במצב. בונדרים עוקבים אחר ישויות אלה עם . 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 The reputation status is determined by slack-based thresholds (defined in ) : ארכיון תגיות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 When an entity is , the bundler limits how many UserOps from that entity can be in the mempool. When , כל UserOps הכוללים את הישות הזאת נדחו באופן מיידי. throttled banned הלחימה ישנם גורמים שיכולים לשפר את המעמד שלהם על ידי הצבת ETH בחוזה EntryPoint: entryPoint.addStake{ value: 1 ether }(unstakeDelaySec); The stake isn't slashed—it's just locked. But it demonstrates commitment and grants: Relaxed storage access rules (as described above) Higher mempool limits גבולות מוניטין גבוהים יותר For paymasters and factories, especially, staking is almost mandatory for production use. Without it, a single failed UserOp can quickly get the entity throttled. The canonical mempool requires (chain-specific, typically 1 ETH or equivalent) and סכום ההימור המדויק משתנה לפי שרשרת והוא מוגדר במטא-נתונים של המפולין – בדוק את תיעוד החבילה עבור הרשת היעד שלך. 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: נקודת המבט של בונדר Revenue = Σ (each UserOp's payment to beneficiary) Cost = Gas used × Gas price paid for handleOps tx Profit = Revenue - Cost Each UserOp pays based on its gas usage and the gas prices it specified: Payment = (actualGasUsed + preVerificationGas) × min(maxFeePerGas, baseFee + maxPriorityFeePerGas) הבונקר מגדיר את address in the call to receive these payments. beneficiary handleOps הבדיקה הסבירה The שדה זה מכסה עלויות שאינן ניתן למדוד ישירות: preVerificationGas עלות Calldata: 16 גזים לכל באט שאינו אפס, 4 גזים לכל באט אפס : Fixed costs per handleOps call that get amortized across UserOps Bundle overhead עלויות נתונים L2: על L2s כמו אופטימיזם או Arbitrum, פרסום נתוני שיחות ל- L1 יש עלויות נוספות 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; הערכים העליונים משתנים לפי חבילה.לפי התייחסות, Alto משתמש , , . Always use rather than hardcoding. transactionGasStipend: 21000 fixedGasOverhead: 9830 perUserOp: 7260 eth_estimateUserOperationGas The Unused Gas Penalty To prevent users from overpaying for gas (which wastes blockspace), the EntryPoint imposes a penalty on unused execution gas. Specifically, the penalty applies to (על מנת לבצע את החשבון) ו (במקרה של תשלומים של PayMaster): callGasLimit paymasterPostOpGasLimit אם גז לא בשימוש בשני השדות עולה על PENALTY_GAS_THRESHOLD (40,000), החשבון משלם 10% (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 כאשר UserOperation נדחה, שגיאת RPC היא האות היחידה שאתה מקבל.Bundlers להחזיר קוד שגיאה מובנה המסביר בדיוק למה המבצע נכשל - אבל רק אם אתה יודע איך לקרוא אותם. AA1x: Factory Errors These occur when deploying a new account via the factory. In EntryPoint v0.8, you specify ו as separate fields (the EntryPoint packs them into באופן פנימי : factory factoryData initCode AA10: "משלוח כבר נבנה" - לכתובת המשלוח כבר יש קוד מיושם. AA13: "initCode נכשל או OOG" - שיחת CreateAccount של המפעל נכשלה או נעלמה. AA14: "initCode חייב להחזיר את השולח" - המפעל החזיר כתובת שונה מהצפוי. : "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: שגיאות אימות חשבונות The most common category: : "account not deployed" - The sender address has no code and no was provided. AA20 initCode : "didn't pay prefund" - The account doesn't have enough ETH to cover the maximum possible gas cost. Fund the account or use a paymaster. AA21 AA22: "נעלם או לא הושלם" - ל- UserOp יש סימן זמן תקף עד שעבר, או סימן זמן תקף לאחר שעוד לא הגיע. AA23: "מוחזר" - הפונקציה ValidateUserOp של החשבון הוחזרה. : "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 שימוש חוזר ב-nonce שכבר נכלל שימוש במפתח nonce הלא נכון UserOp אחר עם אותו שליח ממתין ב- 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 AA31: "הפקדה של paymaster נמוכה מדי" - הפקדה של paymaster ב- EntryPoint לא יכולה לכסות את עלות הגז. entryPoint.depositTo{ value: 1 ether }(paymasterAddress); : "paymaster expired or not due" - Similar to AA22, but for the paymaster's validation data. AA32 : "paymaster reverted" - The paymaster's function reverted. AA33 validatePaymasterUserOp : "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 כל חבילת ERC-4337 מבצעת את השיטות הסטנדרטיות הבאות: : Submit a UserOp for inclusion eth_sendUserOperation const userOpHash = await bundler.request({ method: 'eth_sendUserOperation', params: [userOp, entryPointAddress] }); : Get gas limit estimates 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'] The Shared Mempool Originally, each bundler maintained its own private mempool. This created problems: סיכון צנזורה: חבילה אחת עשויה לסרב לכלול אפשרויות משתמש מסוימות : Users had to know which bundlers to submit to Fragmentation : If your bundler went down, your UserOps were stuck Single points of failure The solution is the , a P2P network where bundlers gossip UserOps to each other. It works similarly to how Ethereum nodes gossip transactions: ERC-4337 shared mempool משתמש שולח UserOp לכל חבילה המשתתפת Bundler Validates ו-Adds to the Local mempool Bundler Broadcasts to Connected Peers – שידורי Bundler ל peers מחוברים Any bundler on the network can include the UserOp. הפרוטוקול משתמש ב- libp2p לרשתות. הבונדלרים מפרסמים את מ-Mepools שהם תומכים בהם (זהים על ידי IPFS CIDs המייצגים קבצי מ-Mepool metadata), ומפיצים רק UserOps שעברו אימות. chainId: '1' entryPointContract: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' description: Canonical ERC-4337 mempool for Ethereum Mainnet minimumStake: '1000000000000000000' IPFS CID של קובץ זה הופך למזהה mempool המשמש בשמות נושאים P2P. מטא-נתונים mempool מגדירים כללים של אימות: אילו אופקודים אסורים, דפוסי גישה לאחסון, גבולות גזים וסף מוניטין. Advanced Topics Aggregators אימות חתימה הוא יקר על שרשרת. ecrecover precompile עולה 3,000 גז לכל שיחה, אבל אימות חתימה חשבונית חכמה בדרך כלל עולה יותר בגלל לוגיקה אימות נוספת – לעתים קרובות 6,000-10,000 גז בסך הכל. עבור 100 UserOps בחבילה, זה 600,000+ גז רק עבור חתימות. What problem do aggregators solve? Instead of each account verifying its own signature, accounts can delegate to an aggregator contract. The aggregator implements a batch verification algorithm (like BLS signatures, where multiple signatures can be combined into one). How it works: Validate של חשבוןUserOp מחזיר כתובת אגרגנטור בנתוני האימות שלה Bundler groups all UserOps using the same aggregator Bundler calls once for the group aggregator.validateSignatures(userOps, aggregatedSignature) אם אימות עובר, כל UserOps בקבוצה זו נחשבים תקפים. : The return value from חבילה של שלושה חלקים של מידע לתוך ערך 256-ביט אחד: 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 אגרגנטור (ביטים 0-159): כתובת של חוזה האגרגנטור, או ערכים מיוחדים: 0 = חתימה חוקית, 1 = חתימה לא חוקית (bits 160-207): Timestamp after which this UserOp expires (0 = no expiry) validUntil validAfter (ביטים 208-255): לוח זמנים לפני אשר UserOp זה אינו תקף (0 = מיד תקף) This encoding lets accounts specify both signature verification delegation and time-bounded validity in a single return value. Paymasters Paymasters תשלום גז abstract ממשתמשים.Instead of the account paying for gas, a paymaster can: : Pay on behalf of users (gasless UX) Sponsor transactions קבל טוקי ERC-20: תן למשתמשים לשלם ב stablecoins או טוקי אחרים יישום לוגיקה מותאמת אישית: הגבלת מחירים, מודלים של מנויים וכו '. זרימת האימות של paymaster פועלת במהלך שלב האימות: function validatePaymasterUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) external returns (bytes memory context, uint256 validationData); The returned here is passed to לאחר שהביצוע מסתיים, המאפשר למנהל התשלום לבצע חשבונאות סופית (כמו חיוב על טוקן ERC-20): 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. יכול בדיקות מקומיות This section assumes you have Anvil running with EntryPoint v0.8 deployed. We'll use , Pimlico's TypeScript bundler, and , a viem-based library for ERC-4337 interactions. Alto permissionless.js SimpleAccountFactory בחלק הראשון יצרנו חשבון חכם מינימלי.אבל איך משתמשים מפעילים אותו?הם לא יכולים לשלוח עסקה קבועה – אין להם ETH עבור גז עדיין. For , the reference implementation includes . Deploy it alongside the EntryPoint before running the examples below. SimpleAccount SimpleAccountFactory Account Deployment via UserOp When the EntryPoint receives a UserOp with factory and factoryData fields: בדוק אם לשלוח יש קוד – אם כן, לעקוף את ההפצה Calls via the factory.createAccount(owner, salt) factoryData Verifies the deployed address matches sender Continues with validation on the newly-deployed account ריצה גבוהה 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 Key flags: : Key for submitting bundles (must have ETH) --executor-private-keys --safe-mode false: Anvil חסר את מעקב JavaScript עבור אימות מלא ERC-7562 : Accept both UserOp formats (v1 for 0.6, v2 for 0.7/0.8) --api-version v1,v2 Sending UserOperations with permissionless.js Install dependencies: npm install viem permissionless Step 1: Set up clients We need three clients: one for reading chain state, one for bundler-specific RPCs, and one for the smart account owner. 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 מחובר ל-RPC של אלטו ומספק הערכה של גז באמצעות . 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) זה מחשב את הכתובת counterfactual באמצעות המפעל של החשבון עדיין לא קיים – אבל אנחנו יודעים בדיוק איפה הוא יושק. 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") }) ה- ETH יושב בכתובת זו.כאשר החשבון מופץ, הוא יכול לגשת לכספים האלה מיד. 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 מתמודדים UserOp בנייה, ניהול nonce, הערכת גז, וחתימה. callback לוקח את מחירי הגז הנוכחי מן החבילה. 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) עבור UserOp הראשון, ה- SDK כולל באופן אוטומטי and EntryPoint מפעילה את החשבון, ולאחר מכן מבצעת את ההעברה – הכל בביצוע עסקה אחת. factory factoryData What We've Learned חבילות הן שכבת הביצוע של ERC-4337.הן מה שהופכות את הפריצה של חשבון מפרט למנגנון מוכן לייצור. הבנת המגבלות שלהם - כללי אימות, כלכלה גז, ומכניקה של מוניטין - היא קריטית בעת עיצוב חשבונות חכמים אמין. 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.