Onchain.AA (onchain v0.7.0)

Copy Markdown View Source

ERC-4337 Account Abstraction: UserOperation construction, hashing, signing, and bundler JSON-RPC.

Supports both EntryPoint versions, whose wire formats differ:

  • v0.6 (0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789) — the original UserOperation with separate callGasLimit/verificationGasLimit and maxFeePerGas/maxPriorityFeePerGas words, plus initCode and paymasterAndData byte fields.
  • v0.7 (0x0000000071727De22E5E9d8BAf0edAc6f37da032) — the PackedUserOperation: accountGasLimits = verificationGasLimit (high 128) ‖ callGasLimit (low 128) and gasFees = maxPriorityFeePerGas (high 128) ‖ maxFeePerGas (low 128), with initCode/paymasterAndData derived from the unpacked factory*/paymaster* fields. The JSON-RPC representation sent to bundlers stays unpacked (separate factory, factoryData, paymasterVerificationGasLimit, etc.).

The version is selected per call via the :version option (:v0_6 | :v0_7, default :v0_7).

userOpHash

Both versions hash as keccak256(abi.encode(keccak256(packed), entryPoint, chainId)), where packed is the version-specific abi.encode of the op (variable-length byte fields hashed with keccak first). This matches EntryPoint.getUserOpHash. Verified against reference vectors in test/onchain/aa_test.exs (cross-checked with viem's getUserOperationHash test vectors).

Signing

sign_user_operation/5 signs the userOpHash and returns the op with its :signature populated. Two schemes:

  • :eip191 (default) — ECDSA over keccak256("\x19Ethereum Signed Message:\n32" ‖ userOpHash), matching the canonical eth-infinitism SimpleAccount (userOpHash.toEthSignedMessageHash()).
  • :raw — ECDSA over the raw 32-byte userOpHash (accounts that recover directly without the EIP-191 envelope).

The signature is r ‖ s ‖ v with v ∈ {27, 28}. Accounts with bespoke signature schemes (Safe, multisig, passkey) should call user_op_hash/4 and build the signature themselves.

Bundler RPC

send_user_operation/3, estimate_user_operation_gas/3, get_user_operation_by_hash/2, get_user_operation_receipt/2, and supported_entry_points/1 wrap the standard bundler methods. The bundler URL is passed via :bundler_url (or :rpc_url); results are returned raw (decoded JSON) since shapes are bundler-defined.

Functions

FunctionPurpose
entry_point/1Canonical EntryPoint address for a version
new/1Build + validate a UserOperation from fields
user_op_hash/4Compute the EntryPoint userOpHash
sign_user_operation/5Sign a UserOperation, return it with :signature
to_rpc_params/2Serialize a UserOperation to bundler JSON-RPC params
send_user_operation/3eth_sendUserOperation → userOpHash
estimate_user_operation_gas/3eth_estimateUserOperationGas → gas map
get_user_operation_by_hash/2eth_getUserOperationByHash
get_user_operation_receipt/2eth_getUserOperationReceipt
supported_entry_points/1eth_supportedEntryPoints → addresses

API Functions

FunctionArityDescriptionParam Kinds
supported_entry_points1List EntryPoint addresses the bundler supports (eth_supportedEntryPoints).opts: value
get_user_operation_receipt2Fetch a UserOperation receipt (eth_getUserOperationReceipt).user_op_hash: value, opts: value
get_user_operation_by_hash2Look up a UserOperation by its hash (eth_getUserOperationByHash).user_op_hash: value, opts: value
estimate_user_operation_gas3Estimate gas for a UserOperation (eth_estimateUserOperationGas).user_op: value, entry_point: value, opts: value
send_user_operation3Submit a UserOperation to a bundler (eth_sendUserOperation).user_op: value, entry_point: value, opts: value
to_rpc_params2Serialize a UserOperation to bundler JSON-RPC params.user_op: value, opts: value
sign_user_operation5Sign a UserOperation and return it with :signature populated.user_op: value, private_key: value, entry_point: value, chain_id: value, opts: value
user_op_hash4Compute the EntryPoint userOpHash for a UserOperation.user_op: value, entry_point: value, chain_id: value, opts: value
new1Build and validate a UserOperation from a map or keyword of fields.fields: value
entry_point1Canonical EntryPoint contract address for a version.version: value

Summary

Functions

Canonical EntryPoint contract address for a version.

Estimate gas for a UserOperation (eth_estimateUserOperationGas).

Look up a UserOperation by its hash (eth_getUserOperationByHash).

Fetch a UserOperation receipt (eth_getUserOperationReceipt).

Build and validate a UserOperation from a map or keyword of fields.

Submit a UserOperation to a bundler (eth_sendUserOperation).

Sign a UserOperation and return it with :signature populated.

List EntryPoint addresses the bundler supports (eth_supportedEntryPoints).

Serialize a UserOperation to bundler JSON-RPC params.

Compute the EntryPoint userOpHash for a UserOperation.

Functions

entry_point(atom)

@spec entry_point(:v0_6 | :v0_7) :: String.t()

Canonical EntryPoint contract address for a version.

Parameters

  • version - EntryPoint version: :v0_6 or :v0_7 (value)

Returns

0x-prefixed checksummed EntryPoint address (string)

# descripex:contract
%{
  params: %{
    version: %{description: "EntryPoint version: :v0_6 or :v0_7", kind: :value}
  },
  returns: %{
    type: :string,
    description: "0x-prefixed checksummed EntryPoint address"
  }
}

estimate_user_operation_gas(user_op, entry_point, opts \\ [])

@spec estimate_user_operation_gas(
  Onchain.AA.UserOperation.t(),
  String.t() | binary(),
  keyword()
) ::
  {:ok, term()} | {:error, term()}

Estimate gas for a UserOperation (eth_estimateUserOperationGas).

Parameters

  • user_op - %Onchain.AA.UserOperation{} struct (signature may be a dummy) (value)
  • entry_point - EntryPoint address the bundler should use (value)
  • opts - Options: :version (default :v0_7), :bundler_url (or :rpc_url), :timeout (default: [], value)

Returns

Gas estimate map (e.g. preVerificationGas, verificationGasLimit, callGasLimit) with bundler-defined hex values ({:ok, map} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :version (default :v0_7), :bundler_url (or :rpc_url), :timeout",
      kind: :value
    },
    entry_point: %{
      description: "EntryPoint address the bundler should use",
      kind: :value
    },
    user_op: %{
      description: "%Onchain.AA.UserOperation{} struct (signature may be a dummy)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map} | {:error, term}",
    description: "Gas estimate map (e.g. preVerificationGas, verificationGasLimit, callGasLimit) with bundler-defined hex values"
  }
}

get_user_operation_by_hash(user_op_hash, opts \\ [])

@spec get_user_operation_by_hash(
  String.t(),
  keyword()
) :: {:ok, term()} | {:error, term()}

Look up a UserOperation by its hash (eth_getUserOperationByHash).

Parameters

  • user_op_hash - 0x-prefixed 32-byte userOpHash (value)
  • opts - Options: :bundler_url (or :rpc_url), :timeout (default: [], value)

Returns

UserOperation + inclusion info map, or nil if the bundler has not seen it ({:ok, map | nil} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :bundler_url (or :rpc_url), :timeout",
      kind: :value
    },
    user_op_hash: %{description: "0x-prefixed 32-byte userOpHash", kind: :value}
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "UserOperation + inclusion info map, or nil if the bundler has not seen it"
  }
}

get_user_operation_receipt(user_op_hash, opts \\ [])

@spec get_user_operation_receipt(
  String.t(),
  keyword()
) :: {:ok, term()} | {:error, term()}

Fetch a UserOperation receipt (eth_getUserOperationReceipt).

Parameters

  • user_op_hash - 0x-prefixed 32-byte userOpHash (value)
  • opts - Options: :bundler_url (or :rpc_url), :timeout (default: [], value)

Returns

Receipt map (success, actualGasUsed, logs, receipt, …), or nil if not yet mined ({:ok, map | nil} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :bundler_url (or :rpc_url), :timeout",
      kind: :value
    },
    user_op_hash: %{description: "0x-prefixed 32-byte userOpHash", kind: :value}
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "Receipt map (success, actualGasUsed, logs, receipt, …), or nil if not yet mined"
  }
}

new(fields)

@spec new(map() | keyword()) :: {:ok, Onchain.AA.UserOperation.t()} | {:error, term()}

Build and validate a UserOperation from a map or keyword of fields.

Parameters

  • fields - Map/keyword of UserOperation fields. Required: :sender (address). Numeric fields (:nonce, :call_gas_limit, :verification_gas_limit, :pre_verification_gas, :max_fee_per_gas, :max_priority_fee_per_gas) default to 0. Byte fields (:init_code, :call_data, :paymaster_and_data, :signature) default to "0x". v0.7 unpacked fields :factory, :factory_data, :paymaster, :paymaster_verification_gas_limit, :paymaster_post_op_gas_limit, :paymaster_data default to nil. (value)

Returns

Validated UserOperation struct, or a validation error ({:ok, %Onchain.AA.UserOperation{}} | {:error, term})

# descripex:contract
%{
  params: %{
    fields: %{
      description: "Map/keyword of UserOperation fields. Required: :sender (address). Numeric fields (:nonce, :call_gas_limit, :verification_gas_limit, :pre_verification_gas, :max_fee_per_gas, :max_priority_fee_per_gas) default to 0. Byte fields (:init_code, :call_data, :paymaster_and_data, :signature) default to \"0x\". v0.7 unpacked fields :factory, :factory_data, :paymaster, :paymaster_verification_gas_limit, :paymaster_post_op_gas_limit, :paymaster_data default to nil.",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, %Onchain.AA.UserOperation{}} | {:error, term}",
    description: "Validated UserOperation struct, or a validation error"
  }
}

send_user_operation(user_op, entry_point, opts \\ [])

@spec send_user_operation(
  Onchain.AA.UserOperation.t(),
  String.t() | binary(),
  keyword()
) ::
  {:ok, term()} | {:error, term()}

Submit a UserOperation to a bundler (eth_sendUserOperation).

Parameters

  • user_op - Signed %Onchain.AA.UserOperation{} struct (value)
  • entry_point - EntryPoint address the bundler should use (value)
  • opts - Options: :version (default :v0_7), :bundler_url (or :rpc_url), :timeout (default: [], value)

Returns

0x-prefixed userOpHash returned by the bundler ({:ok, String.t()} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :version (default :v0_7), :bundler_url (or :rpc_url), :timeout",
      kind: :value
    },
    entry_point: %{
      description: "EntryPoint address the bundler should use",
      kind: :value
    },
    user_op: %{
      description: "Signed %Onchain.AA.UserOperation{} struct",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term}",
    description: "0x-prefixed userOpHash returned by the bundler"
  }
}

sign_user_operation(user_op, private_key, entry_point, chain_id, opts \\ [])

@spec sign_user_operation(
  Onchain.AA.UserOperation.t(),
  binary(),
  String.t() | binary(),
  pos_integer(),
  keyword()
) :: {:ok, Onchain.AA.UserOperation.t()} | {:error, term()}

Sign a UserOperation and return it with :signature populated.

Parameters

  • user_op - %Onchain.AA.UserOperation{} struct (value)
  • private_key - 32-byte binary or hex private key (with or without 0x) (value)
  • entry_point - EntryPoint address (hex string or 20-byte binary) (value)
  • chain_id - Chain ID integer (value)
  • opts - Options: :version (:v0_6 | :v0_7, default :v0_7), :scheme (:eip191 default, or :raw to sign the userOpHash directly) (default: [], value)

Returns

UserOperation with :signature set to a 65-byte r‖s‖v hex string ({:ok, %Onchain.AA.UserOperation{}} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :version (:v0_6 | :v0_7, default :v0_7), :scheme (:eip191 default, or :raw to sign the userOpHash directly)",
      kind: :value
    },
    private_key: %{
      description: "32-byte binary or hex private key (with or without 0x)",
      kind: :value
    },
    entry_point: %{
      description: "EntryPoint address (hex string or 20-byte binary)",
      kind: :value
    },
    user_op: %{description: "%Onchain.AA.UserOperation{} struct", kind: :value},
    chain_id: %{description: "Chain ID integer", kind: :value}
  },
  returns: %{
    type: "{:ok, %Onchain.AA.UserOperation{}} | {:error, term}",
    description: "UserOperation with :signature set to a 65-byte r‖s‖v hex string"
  }
}

supported_entry_points(opts \\ [])

@spec supported_entry_points(keyword()) :: {:ok, term()} | {:error, term()}

List EntryPoint addresses the bundler supports (eth_supportedEntryPoints).

Parameters

  • opts - Options: :bundler_url (or :rpc_url), :timeout (default: [], value)

Returns

List of 0x-prefixed EntryPoint addresses ({:ok, [String.t()]} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :bundler_url (or :rpc_url), :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, [String.t()]} | {:error, term}",
    description: "List of 0x-prefixed EntryPoint addresses"
  }
}

to_rpc_params(user_op, opts \\ [])

@spec to_rpc_params(
  Onchain.AA.UserOperation.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}

Serialize a UserOperation to bundler JSON-RPC params.

Parameters

  • user_op - %Onchain.AA.UserOperation{} struct (value)
  • opts - Options: :version (:v0_6 | :v0_7, default :v0_7) (default: [], value)

Returns

JSON-RPC UserOperation object with string keys and 0x-quantity numeric fields. v0.7 includes factory/paymaster fields only when set. ({:ok, map} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :version (:v0_6 | :v0_7, default :v0_7)",
      kind: :value
    },
    user_op: %{description: "%Onchain.AA.UserOperation{} struct", kind: :value}
  },
  returns: %{
    type: "{:ok, map} | {:error, term}",
    description: "JSON-RPC UserOperation object with string keys and 0x-quantity numeric fields. v0.7 includes factory/paymaster fields only when set."
  }
}

user_op_hash(user_op, entry_point, chain_id, opts \\ [])

@spec user_op_hash(
  Onchain.AA.UserOperation.t(),
  String.t() | binary(),
  pos_integer(),
  keyword()
) ::
  {:ok, String.t()} | {:error, term()}

Compute the EntryPoint userOpHash for a UserOperation.

Parameters

  • user_op - %Onchain.AA.UserOperation{} struct (value)
  • entry_point - EntryPoint address (hex string or 20-byte binary) (value)
  • chain_id - Chain ID integer (1 = mainnet, 11155111 = Sepolia) (value)
  • opts - Options: :version (:v0_6 | :v0_7, default :v0_7) (default: [], value)

Returns

0x-prefixed 32-byte userOpHash ({:ok, String.t()} | {:error, term})

# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :version (:v0_6 | :v0_7, default :v0_7)",
      kind: :value
    },
    entry_point: %{
      description: "EntryPoint address (hex string or 20-byte binary)",
      kind: :value
    },
    user_op: %{description: "%Onchain.AA.UserOperation{} struct", kind: :value},
    chain_id: %{
      description: "Chain ID integer (1 = mainnet, 11155111 = Sepolia)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term}",
    description: "0x-prefixed 32-byte userOpHash",
    example: "0x1903d62bb5dc75af6fed866aa46d8e80063d9e288aa7f2caad0ff1fcae22e40d"
  }
}