στην , ένας έξυπνος λογαριασμός αναπτύχθηκε και η πρώτη UserOperation εκτελέστηκε με επιτυχία μέσω του EntryPoint. Σε αυτό το σημείο, όλα λειτούργησαν - αλλά ένα κρίσιμο μέρος του συστήματος παρέμεινε ως επί το πλείστον αόρατο: το bundler. Μέρος 1 Οι συσσωρευτές είναι η γέφυρα μεταξύ της αφαίρεσης λογαριασμών και του επιπέδου εκτέλεσης του Ethereum. Λαμβάνουν τις λειτουργίες των χρηστών από ένα ξεχωριστό 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 Ο κύκλος ζωής μιας UserOperation μοιάζει με αυτό: Δημιουργία: Ο χρήστης δημιουργεί το UserOp με το SDK του έξυπνου λογαριασμού του (όπως permissionless.js) Υπογραφή: Ο χρήστης υπογράφει το hash UserOp, αποδεικνύοντας ότι εξουσιοδότησε την ενέργεια Υποβολή: Το UserOp αποστέλλεται σε ένα δέμα μέσω του eth_sendUserOperation Επιβεβαίωση: Ο Bundler προσομοιώνει το UserOp για να ελέγξει αν θα πετύχει : If valid, the UserOp enters the bundler's mempool Mempool Bundling: Bundler πακέτα πολλαπλών UserOps σε μία μόνο κλήση handleOps Εκτέλεση: Η σύμβαση EntryPoint επικυρώνει κάθε UserOp στην αλυσίδα και στη συνέχεια τις εκτελεί Πληρωμή: Το EntryPoint συλλέγει τα έξοδα φυσικού αερίου από κάθε λογαριασμό (ή τον διαχειριστή πληρωμών). Η βασική γνώση είναι ότι η επικύρωση συμβαίνει δύο φορές: μία φορά εκτός αλυσίδας από το bundler (για να αποφασίσει αν θα αποδεχθεί το UserOp), και μία φορά σε αλυσίδα από το EntryPoint (πριν πραγματικά εκτελέσει). 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. Βεβαίωση: Γιατί οι Bundlers Είναι Παρανοϊκοί Οι συσσωρευτές πληρώνουν το κόστος του φυσικού αερίου εκ των προτέρων. Εάν μια UserOperation αποτύχει στην αλυσίδα αφού συμπεριληφθεί σε ένα πακέτο, η απώλεια είναι δική τους. Αυτό το μοναδικό γεγονός καθορίζει ολόκληρο το μοντέλο απειλής bundler. Μεταξύ προσομοίωσης και ενσωμάτωσης, η κατάσταση του Ethereum δεν είναι στατική. Οι παράμετροι μπλοκ μετατοπίζονται, τα υπόλοιπα αλλάζουν και οι ανταγωνιστικές συναλλαγές μπορούν να προσγειωθούν μεταξύ τους. Μια προσεκτικά σχεδιασμένη UserOperation μπορεί να περάσει από την προσομοίωση αλυσίδας και εξακολουθεί να αποτυγχάνει κατά τη διάρκεια της επικύρωσης στην αλυσίδα - μετατρέποντας το bundler σε μια βρύση αερίου. Το ERC-4337 ανταποκρίνεται περιορίζοντας έντονα τι επιτρέπεται να κάνει ο κώδικας επικύρωσης. Το EntryPoint επιβάλλει αυστηρό διαχωρισμό ανησυχιών: : Η λειτουργία validateUserOp του λογαριασμού εκτελείται για να επαληθεύσει την υπογραφή και να εξουσιοδοτήσει τη λειτουργία.Αυτή η φάση έχει αυστηρούς περιορισμούς για το ποιους κωδικούς και αποθήκευση μπορεί να έχει πρόσβαση ο κώδικας. Validation Phase : Η λειτουργία εκτέλεσης του λογαριασμού εκτελεί την πραγματική λειτουργία. Δεν υπάρχουν περιορισμοί εδώ – πλήρεις δυνατότητες EVM. Execution Phase Απαγορευμένοι Κωδικοί Ορισμένοι κωδικοί επιλογής απαγορεύονται κατά τη διάρκεια της επικύρωσης επειδή οι τιμές τους μπορεί να αλλάξουν μεταξύ της προσομοίωσης και της εκτέλεσης: TIMESTAMP, NUMBER, COINBASE, PREVRANDAO: Αξίες που εξαρτώνται από το μπλοκ. Ένας λογαριασμός θα μπορούσε να ελέγξει αν (block.timestamp > deadline) επιστρέψει, να περάσει την προσομοίωση και στη συνέχεια να αποτύχει όταν το δέμα προσγειωθεί σε ένα μεταγενέστερο μπλοκ. : Returns different values for different blocks. Same attack vector. BLOCKHASH GASPRICE, BASEFEE: Αλλαγή με βάση τις συνθήκες του δικτύου. Ισορροπία, αυτοεξισορρόπηση: Επιτρέπεται μόνο από ενδιαφερόμενες οντότητες (βλέπε Απεργία παρακάτω) ανά ERC-7562 [OP-080]. GASLIMIT, ORIGIN: Μπορεί να διαφέρει μεταξύ του περιβάλλοντος προσομοίωσης και της πραγματικής εκτέλεσης. BLOBHASH, BLOBBASEFEE: EIP-4844 blob-σχετιζόμενοι κώδικες που ποικίλλουν ανά μπλοκ. SELFDESTRUCT, INVALID: Καταστρεπτικοί κωδικοί δεν επιτρέπονται κατά τη διάρκεια της επικύρωσης. ΑΕΡΟΣ: Επιτρέπεται μόνο όταν ακολουθείται αμέσως από μια εντολή *CALL—CALL, DELEGATECALL, STATICCALL, ή CALLCODE (για τη μεταφορά αερίου σε άλλες συμβάσεις). : Generally banned because it creates contracts at unpredictable addresses. However, is allowed for the sender contract when using an unstaked factory. is permitted exactly once during deployment for the sender contract. CREATE CREATE CREATE2 Εδώ είναι τι συμβαίνει όταν ο λογαριασμός σας χρησιμοποιεί ένα απαγορευμένο 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 } Το bundler εκτελεί ένα ίχνος κατά τη διάρκεια της προσομοίωσης (χρησιμοποιώντας 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 Κανόνες πρόσβασης αποθήκευσης Πέρα από τους κώδικες, οι συσκευασίες περιορίζουν ποιοι κώδικες επικύρωσης για τις υποδοχές αποθήκευσης μπορούν να διαβαστούν και να γραφτούν. can only access: Unstaked entities Η δική τους αποθήκευση (η αποθήκευση του λογαριασμού) 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 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 . The actual storage slot is Εάν η διεύθυνση του λογαριασμού σας είναι το κλειδί, αυτή η υποδοχή είναι "συνδεδεμένη" με εσάς. Η αντιστάθμιση (0-128) επιτρέπει την πρόσβαση στα μέλη δομής που είναι αποθηκευμένα μετά την καταχώρηση χαρτογράφησης – χρήσιμη όταν η τιμή χαρτογράφησης είναι δομή. mapping(address => uint256) balances keccak256(address || slot_of_mapping) +n (οι λογαριασμοί, οι διαχειριστές πληρωμών ή τα εργοστάσια που έχουν καταθέσει μερίδιο στο EntryPoint) αποκτούν περισσότερη ελευθερία: Staked entities Μπορεί να έχει πρόσβαση σε οποιαδήποτε υποδοχή στη δική του αποθήκευση (STO-031) Can access the associated storage of any entity in the UserOp (STO-032) Μπορεί να έχει μόνο ανάγνωση πρόσβασης σε αποθήκευση σε συμβάσεις μη οντοτήτων (STO-033) See the full storage access rules in . ΕΡΤ 7562 Γιατί υπάρχουν αυτοί οι κανόνες; Σκεφτείτε δύο UserOperations που διαβάζουν από την ίδια υποδοχή αποθήκευσης κατά τη διάρκεια της επικύρωσης. Εάν η πρώτη πράξη μεταλλάσσει αυτή την υποδοχή, οι υποθέσεις επικύρωσης της δεύτερης πράξης μπορεί να μην ισχύουν πλέον. Με τον περιορισμό της πρόσβασης αποθήκευσης κατά τη διάρκεια της επικύρωσης, οι συσκευασίες μπορούν να συμπεριλάβουν με ασφάλεια πολλαπλές λειτουργίες χρήστη στο ίδιο πακέτο χωρίς τον κίνδυνο διασταυρούμενης παρεμβολής ή μη καθοριστικών αποτυχιών. The Reputation System Ακόμη και με κανόνες επικύρωσης, οι οντότητες μπορούν να συμπεριφέρονται άσχημα. Ένας λογαριασμός μπορεί να υποβάλλει συνεχώς UserOps που περάσουν προσομοίωση αλλά αποτυγχάνουν στην αλυσίδα λόγω λεπτών αλλαγών κατάστασης. ένας paymaster μπορεί να εγκρίνει UserOps κατά τη διάρκεια της προσομοίωσης αλλά να αρνηθεί την πληρωμή στην αλυσίδα. Οι Bundlers παρακολουθούν αυτές τις οντότητες με ένα Για κάθε οντότητα (διεύθυνση λογαριασμού, διεύθυνση paymaster, διεύθυνση εργοστασίου), το bundler παρακολουθεί: reputation system opsSeen: Πόσα UserOps που περιλαμβάνουν αυτή την οντότητα έχει δει το bundler : How many of those actually got included on-chain opsIncluded Η κατάσταση της φήμης καθορίζεται από τα κατώτατα όρια (που ορίζονται σε ) : ΕΡΤ 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 Οι τιμές "slack" είναι τα απορρίμματα ανοχής που εμποδίζουν τα ψευδώς θετικά από την κανονική λειτουργική διακύμανση. σημαίνει ότι μια οντότητα μπορεί να έχει έως και 10 περισσότερες αναμενόμενες ενσωματώσεις από ό, τι πραγματικές ενσωματώσεις πριν από την εξαγωγή. Αυτό το σχέδιο αναγνωρίζει ότι ορισμένα UserOps αποτυγχάνουν νόμιμα (συνθήκες δικτύου, συνθήκες αγώνα) χωρίς να υποδεικνύουν κακόβουλη συμπεριφορά. THROTTLING_SLACK = 10 BAN_SLACK = 50 Όταν μια οντότητα είναι , 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); Το στοίχημα δεν κόβεται – είναι απλά κλειδωμένο, αλλά δείχνει δέσμευση και επιχορηγήσεις: Απλοποιημένοι κανόνες πρόσβασης στην αποθήκευση (όπως περιγράφεται παραπάνω) Higher mempool limits More lenient reputation thresholds 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 Το ακριβές ποσό του μεριδίου ποικίλλει ανά αλυσίδα και ορίζεται στα μεταδεδομένα του mempool – ελέγξτε την τεκμηρίωση δέσμης για το δίκτυο στόχου σας. MIN_STAKE_VALUE MIN_UNSTAKE_DELAY οικονομία φυσικού αερίου Πληρώνουν το κόστος του φυσικού αερίου για να υποβάλουν δέσμες και να επιστραφούν από τα UserOps που περιλαμβάνουν. Η προοπτική του Bundler Revenue = Σ (each UserOp's payment to beneficiary) Cost = Gas used × Gas price paid for handleOps tx Profit = Revenue - Cost Κάθε UserOp πληρώνει με βάση τη χρήση του φυσικού αερίου και τις τιμές φυσικού αερίου που καθορίζει: Payment = (actualGasUsed + preVerificationGas) × min(maxFeePerGas, baseFee + maxPriorityFeePerGas) Ο Μπάντλερ καθορίζει το Η διεύθυνση στο καλούνται να λάβουν αυτές τις πληρωμές. beneficiary handleOps preVerificationGas Explained Η field covers costs that can't be directly metered: preVerificationGas Κόστος τηλεφωνικών δεδομένων: 16 αέρια ανά μη μηδενικό byte, 4 αέρια ανά μηδενικό byte Συνολικό κόστος: Σταθερά έξοδα ανά κλήση χειρισμού που αποσβέζονται σε UserOps Χρεώσεις δεδομένων L2: Σε L2s όπως το Optimism ή το Arbitrum, η αποστολή δεδομένων κλήσης στο L1 έχει πρόσθετα έξοδα Κατά την εκτίμηση του αερίου, οι συσσωρευτές υπολογίζουν το preVerificationGas με βάση το μέγεθος του UserOp: // 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; Οι γενικές τιμές ποικίλλουν ανάλογα με το bundler. Για αναφορά, το Alto χρησιμοποιεί , , . Always use rather than hardcoding. transactionGasStipend: 21000 fixedGasOverhead: 9830 perUserOp: 7260 eth_estimateUserOperationGas Τα πρόστιμα για το αχρησιμοποίητο αέριο To prevent users from overpaying for gas (which wastes blockspace), the EntryPoint imposes a penalty on unused execution gas. Specifically, the penalty applies to (για την εκτέλεση λογαριασμών) και (for paymaster post-operations): 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 Αυτό αποθαρρύνει τον καθορισμό παράλογα υψηλών ορίων εκτέλεσης "μόνο για να είναι ασφαλές" When you see gas estimation errors, check if your limits are reasonable. The bundler's Το τελικό σημείο παρέχει εύλογες ελλείψεις. eth_estimateUserOperationGas Συνηθισμένα λάθη και 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: Εργοστασιακά σφάλματα Αυτά συμβαίνουν όταν αναπτύσσετε έναν νέο λογαριασμό μέσω του εργοστασίου. Στο EntryPoint v0.8, καθορίζετε και ως ξεχωριστά πεδία (το EntryPoint τα συσκευάζει σε Εσωτερικά : factory factoryData initCode AA10: "ο αποστολέας έχει ήδη κατασκευαστεί" - Η διεύθυνση του αποστολέα έχει ήδη αναπτύξει κώδικα. AA13: "initCode απέτυχε ή OOG" - Η κλήση δημιουργίας λογαριασμού του εργοστασίου απέτυχε ή έπαυσε να λειτουργεί. AA14: "initCode must return sender" - Το εργοστάσιο επέστρεψε μια διαφορετική διεύθυνση από την αναμενόμενη. AA15: "initCode must create sender" - Η εργοστασιακή κλήση ολοκληρώθηκε αλλά δεν αναπτύχθηκε κώδικας στη διεύθυνση του αποστολέα. : Check that your factory's Η λειτουργία επιστρέφει την αναμενόμενη διεύθυνση. Βεβαιωθείτε ότι το εργοστάσιο έχει αναπτυχθεί και χρηματοδοτηθεί. Debugging createAccount AA2x: Λάθη επικύρωσης λογαριασμού The most common category: AA20: "Λογαριασμός δεν έχει αναπτυχθεί" - Η διεύθυνση αποστολής δεν έχει κωδικό και δεν παρέχεται initCode. AA21: "δεν πλήρωσε προχρηματοδότηση" - Ο λογαριασμός δεν έχει αρκετό ETH για να καλύψει το μέγιστο δυνατό κόστος φυσικού αερίου. : "expired or not due" - The UserOp has a timestamp that has passed, or a timestamp that hasn't arrived yet. AA22 validUntil validAfter AA23: "επιστρέφεται" - Η λειτουργία validateUserOp του λογαριασμού έχει επιστρέψει. AA24: "Σφάλμα υπογραφής" - Τα δεδομένα επικύρωσης που επιστρέφονται υποδεικνύουν άκυρη υπογραφή. Το nonce στο ERC-4337 είναι μια τιμή 256 bit με δύο μέρη: nonce = (κλειδί << 64) ράβδος. Το κλειδί (πάνω από 192 bits) προσδιορίζει το "lane" - μπορείτε να έχετε πολλαπλές παράλληλες UserOps με διαφορετικά κλειδιά. Η ακολουθία (κάτω από 64 bits) πρέπει να αυξάνεται διαδοχικά μέσα σε κάθε lane. Κοινές αιτίες: Επαναχρησιμοποίηση ενός nonce που είχε ήδη συμπεριληφθεί Χρησιμοποιώντας το λάθος κλειδί nonce Ένα άλλο UserOp με τον ίδιο αποστολέα αναμένεται στο mempool AA26: "over verificationGasLimit" - η επικύρωση του λογαριασμού χρησιμοποίησε περισσότερο αέριο από ό, τι διατέθηκε. AA3x: Paymaster Errors AA30: "paymaster δεν έχει αναπτυχθεί" - Η διεύθυνση paymaster δεν έχει αναπτυχθεί κώδικα. 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 AA33: "paymaster reverted" - Η λειτουργία validatePaymasterUserOp του paymaster έχει αντιστραφεί. : "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 Every ERC-4337 bundler implements these standard methods: : Υποβολή UserOp για ενσωμάτωση 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'] Η κοινή μεμβράνη Αρχικά, κάθε bundler διατηρούσε τη δική του ιδιωτική mempool. Αυτό δημιούργησε προβλήματα: : A single bundler could refuse to include certain UserOps Censorship risk Κατακερματισμός: Οι χρήστες έπρεπε να γνωρίζουν σε ποια δέσμη να υποβάλουν Μοναδικά σημεία αποτυχίας: Εάν το πακέτο σας κατέρρευσε, τα UserOps σας ήταν κολλημένα Η λύση είναι η , ένα δίκτυο P2P όπου οι συσσωρευτές κοροϊδεύουν UserOps μεταξύ τους. Λειτουργεί παρόμοια με τον τρόπο με τον οποίο οι κόμβοι Ethereum κοροϊδεύουν συναλλαγές: ERC-4337 shared mempool Ο χρήστης υποβάλλει το UserOp σε οποιοδήποτε συμμετέχον bundler Bundler επικυρώνει και προσθέτει στο τοπικό mempool Bundler broadcasts to connected peers Any bundler on the network can include the 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' The IPFS CID of this file becomes the mempool identifier used in P2P topic names. The mempool metadata defines validation rules: which opcodes are banned, storage access patterns, gas limits, and reputation thresholds. When a bundler receives a UserOp via P2P gossip, it re-validates against its own rules before adding to its local mempool. Advanced Topics Aggregators Signature verification is expensive on-chain. The ecrecover precompile costs 3,000 gas per call, but smart account signature verification typically costs more due to additional validation logic—often 6,000-10,000 gas total. For 100 UserOps in a bundle, that's 600,000+ gas just for signatures. Aggregators enable batch signature verification—verify all 100 signatures in a single operation for a fraction of the cost. What problem do aggregators solve? Αντί για κάθε λογαριασμό που επαληθεύει τη δική του υπογραφή, οι λογαριασμοί μπορούν να αναθέσουν σε σύμβαση συγκεντρωτή.Ο συγκεντρωτής εφαρμόζει έναν αλγόριθμο επαλήθευσης παρτίδας (όπως οι υπογραφές BLS, όπου πολλαπλές υπογραφές μπορούν να συνδυαστούν σε μία). How it works: Account's returns an aggregator address in its validation data validateUserOp Bundler Ομάδες όλων των UserOps χρησιμοποιώντας τον ίδιο συσσωρευτή Bundler καλεί aggregator.validateSignatures(userOps, aggregatedSignature) μία φορά για την ομάδα Εάν η επαλήθευση περάσει, όλα τα UserOps σε αυτή την ομάδα θεωρούνται έγκυρα Η αξία επιστροφής από 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 (bits 160-207): Timestamp after which this UserOp expires (0 = no expiry) validUntil validAfter (bit 208-255): Σφραγίδα χρόνου πριν από την οποία αυτό το UserOp δεν είναι έγκυρο (0 = άμεσα έγκυρο) This encoding lets accounts specify both signature verification delegation and time-bounded validity in a single return value. Οι paymasters Οι paymasters αφηγούνται την πληρωμή φυσικού αερίου από τους χρήστες. Αντί να πληρώνουν το λογαριασμό για το φυσικό αέριο, ένας paymaster μπορεί: : Pay on behalf of users (gasless UX) Sponsor transactions Αποδοχή ERC-20 tokens: Αφήστε τους χρήστες να πληρώσουν σε stablecoins ή άλλα tokens Εφαρμογή προσαρμοσμένης λογικής: περιορισμός τιμών, μοντέλα συνδρομής κ.λπ. 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); Η returned here is passed to μετά την ολοκλήρωση της εκτέλεσης, επιτρέποντας στον πληρωτή να εκτελέσει την τελική λογιστική (όπως η χρέωση ενός ERC-20 token): 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 λειτουργεί τεχνικά με τις βασικές λειτουργίες, το στοίχημα είναι σχεδόν απαραίτητο για οποιαδήποτε σοβαρή εφαρμογή paymaster. Μπορεί Testing Locally This section assumes you have Anvil running with EntryPoint v0.8 deployed. We'll use , το bundler TypeScript του Pimlico, και , a viem-based library for ERC-4337 interactions. Υψηλός permissionless.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. Για , the reference implementation includes . Deploy it alongside the EntryPoint before running the examples below. SimpleAccount Εύκολο εργοστάσιο 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 Running Alto 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 tracer για την πλήρη επικύρωση του ERC-7562 : Accept both UserOp formats (v1 for 0.6, v2 for 0.7/0.8) --api-version v1,v2 Αποστολή UserOperations με permissionless.js Install dependencies: npm install viem permissionless Step 1: Set up clients Χρειαζόμαστε τρεις πελάτες: έναν για την κατάσταση της αλυσίδας ανάγνωσης, έναν για τα RPC που σχετίζονται με το bundler και έναν για τον ιδιοκτήτη έξυπνου λογαριασμού. 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 Ο λογαριασμός δεν υπάρχει ακόμα – αλλά γνωρίζουμε ακριβώς πού θα αναπτυχθεί. 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 } }) Η handles UserOp construction, nonce management, gas estimation, and signing. The callback fetches current gas prices from the bundler. 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 fields. The EntryPoint deploys the account, then executes the transfer—all in one transaction. 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. Understanding their constraints — validation rules, gas economics, and reputation mechanics — is critical when designing reliable smart accounts. Mistakes here don’t surface in Solidity code, but in mempool behavior, simulations, and execution economics. 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.