1 of 19

Unlocking Account Abstraction

Smart accounts for the next billion users

Dror Tirosh

ERC-4337 Core Team

2 of 19

What is Account Abstraction?

What are we abstracting exactly, and why?

3 of 19

What We Abstract ?

  • Validation
    • validateUserOp(...)
  • Execution
    • executeBatch(...)
  • Gas Payment
    • paymaster
    • validatePaymasterUserOp(...)

4 of 19

The Main Components of ERC-4337

Bundler

EntryPoint Contract

Account Contract

Paymaster

UserOperation

UserOp Mempool

5 of 19

UserOperation Lifecycle

Bundler

User’s Contract Account

validateUserOp()

execute()

EntryPoint Contract��simulateUserOp()handleOps()

Paymaster Contract��validatePaymasterUserOp()�postOp()

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

UserOp mempool

Crypto wallet image by Frühstück from Noun Project

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

from:

calldata:

paymaster:

signature:

simulateUserOp()

Simulation Phase

(“dry run”: view calls only)

validateUserOp()

validatePaymasterUserOp()

Execution Phase

handleOps()

validateUserOp()

validatePaymasterUserOp()

execute()

postOp()

6 of 19

EOA txs VS UserOperations

Plain old Accounts (EOA)

  • Account
    • Secured by ECDSA
  • Wallet
    • “Approve” to sign with ECDSA
  • Mempool
    • In-memory transactions, of all nodes
  • Block-builder
    • Verify ECDSA
    • Verify balance (for gas payment)
    • Add to block�

Account-Abstraction

  • Account
    • Secured by its own CODE
  • Wallet
    • Custom UI depending on account.
  • Mempool
    • In-memory UserOperations of all nodes
  • Bundler
    • Off chain validation : validateUserOp
    • Off chain validation: validatePaymasterUserOp
    • Add to bundle

7 of 19

BaseAccount.sol

function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)

external override virtual returns (uint256 validationData) {

_requireFromEntryPoint();

validationData = _validateSignature(userOp, userOpHash);

if (userOp.initCode.length == 0) {

_validateAndUpdateNonce(userOp);

}

_payPrefund(missingAccountFunds);

}

function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)

internal virtual returns (uint256 validationData);

function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter)

pure returns (uint256);

8 of 19

SimpleAccount: Validation

function _validateAndUpdateNonce(UserOperation calldata userOp) internal override {

require(_nonce++ == userOp.nonce, "account: invalid nonce");

}

function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)

internal override virtual returns (uint256 validationData) {

bytes32 hash = userOpHash.toEthSignedMessageHash();

if (owner != hash.recover(userOp.signature))

return SIG_VALIDATION_FAILED;

return 0;

}

9 of 19

SimpleAccount: Execution

function execute(address dest, uint256 value, bytes calldata func) external {

_requireFromEntryPointOrOwner();

_call(dest, value, func);

}

function executeBatch(address[] calldata dest, bytes[] calldata func) external {

_requireFromEntryPointOrOwner();

require(dest.length == func.length, "wrong array lengths");

for (uint256 i = 0; i < dest.length; i++) {

_call(dest[i], 0, func[i]);

}

}

10 of 19

BasePaymaster.sol

function validatePaymasterUserOp(UserOperation userOp, bytes32 userOpHash, uint256 maxCost)

external override returns (bytes memory context, uint256 validationData) {

_requireFromEntryPoint();

return _validatePaymasterUserOp(userOp, userOpHash, maxCost);

}

function postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) external override {

_requireFromEntryPoint();

_postOp(mode, context, actualGasCost);

}

11 of 19

The Possibilities are Endless

  • Gaming: session keys (limited target, limited time, UI-less)
  • Corporate: (multiple signers, multiple roles)
  • For more examples, check out the following presentations from ETH Bogota:
    • https://youtu.be/HbNdGex47ks

12 of 19

Introducing: Trampoline

  • Boilerplate code for 4337-enabled browser extension wallet
  • http://github.com/eth-infinitism/trampoline
  • Inspired by Tally-Ho, coded by Garvit (twitter: @plusminushalf)
  • Architecture
    • Custom contract (extend/replace SimpleAccount)
    • Custom AccountAPI engine (based on BaseAccountAPI
      • account-api
    • UI components
      • components/onboarding
      • components/transaction
      • components/sign-message

13 of 19

Trampoline: account-api

  • Based on SimpleAccountAPI
    • Constructed from “onboarding” context
    • getAccountInitCode()
    • getNonce()
    • encodeExecute()
    • signUserOpWithContext()

14 of 19

Trampoline: Onboarding flow - add chrome extension

  • Execute “components/onboarding”
    • Display custom UX to user
    • onBoardingComplete(context)
  • Create account (account-api) with context (in the worker thread)

15 of 19

Trampoline: Transaction Flow

  • Dapp initiates eth_sendTransaction rpc call
  • Launch components/transaction
    • Display custom UX and collect “context”
    • onComplete(context)
  • Framework creates UserOperation
  • Framework calls “account.signUserOperationWithContext(context)”
  • Framework sends userOperation to bundler.

16 of 19

Demo Time!

17 of 19

“It takes a village”

Developer platforms already making ERC-4337 a reality

18 of 19

ERC-4337 Developer Platforms

  • ZeroDev
  • UniPass

19 of 19

Going to BUIDL? We’re here to help