Prechádzať zdrojové kódy

Add Account framework docs and guides (#5660)

Ernesto García 3 mesiacov pred
rodič
commit
6ef73e3386

+ 5 - 5
contracts/account/extensions/draft-AccountERC7579.sol

@@ -237,11 +237,11 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
      *
      * Requirements:
      *
-     * * Module type must be supported. See {supportsModule}. Reverts with {ERC7579UnsupportedModuleType}.
-     * * Module must be of the given type. Reverts with {ERC7579MismatchedModuleTypeId}.
-     * * Module must not be already installed. Reverts with {ERC7579AlreadyInstalledModule}.
+     * * Module type must be supported. See {supportsModule}. Reverts with {ERC7579Utils-ERC7579UnsupportedModuleType}.
+     * * Module must be of the given type. Reverts with {ERC7579Utils-ERC7579MismatchedModuleTypeId}.
+     * * Module must not be already installed. Reverts with {ERC7579Utils-ERC7579AlreadyInstalledModule}.
      *
-     * Emits a {ModuleInstalled} event.
+     * Emits a {IERC7579ModuleConfig-ModuleInstalled} event.
      */
     function _installModule(uint256 moduleTypeId, address module, bytes memory initData) internal virtual {
         require(supportsModule(moduleTypeId), ERC7579Utils.ERC7579UnsupportedModuleType(moduleTypeId));
@@ -276,7 +276,7 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
      *
      * Requirements:
      *
-     * * Module must be already installed. Reverts with {ERC7579UninstalledModule} otherwise.
+     * * Module must be already installed. Reverts with {ERC7579Utils-ERC7579UninstalledModule} otherwise.
      */
     function _uninstallModule(uint256 moduleTypeId, address module, bytes memory deInitData) internal virtual {
         require(supportsModule(moduleTypeId), ERC7579Utils.ERC7579UnsupportedModuleType(moduleTypeId));

+ 20 - 0
contracts/mocks/docs/account/MyAccountERC7702.sol

@@ -0,0 +1,20 @@
+// contracts/MyAccountERC7702.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {Account} from "../../../account/Account.sol";
+import {ERC721Holder} from "../../../token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "../../../token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7821} from "../../../account/extensions/draft-ERC7821.sol";
+import {SignerERC7702} from "../../../utils/cryptography/signers/SignerERC7702.sol";
+
+contract MyAccountERC7702 is Account, SignerERC7702, ERC7821, ERC721Holder, ERC1155Holder {
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}

+ 37 - 0
contracts/mocks/docs/account/MyFactoryAccount.sol

@@ -0,0 +1,37 @@
+// contracts/MyFactoryAccount.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+import {Clones} from "../../../proxy/Clones.sol";
+import {Address} from "../../../utils/Address.sol";
+
+/**
+ * @dev A factory contract to create accounts on demand.
+ */
+contract MyFactoryAccount {
+    using Clones for address;
+    using Address for address;
+
+    address private immutable _impl;
+
+    constructor(address impl_) {
+        require(impl_.code.length > 0);
+        _impl = impl_;
+    }
+
+    /// @dev Predict the address of the account
+    function predictAddress(bytes calldata callData) public view returns (address) {
+        return _impl.predictDeterministicAddress(keccak256(callData), address(this));
+    }
+
+    /// @dev Create clone accounts on demand
+    function cloneAndInitialize(bytes calldata callData) public returns (address) {
+        address predicted = predictAddress(callData);
+        if (predicted.code.length == 0) {
+            _impl.cloneDeterministic(keccak256(callData));
+            predicted.functionCall(callData);
+        }
+        return predicted;
+    }
+}

+ 2 - 1
contracts/utils/cryptography/signers/MultiSignerERC7913.sol

@@ -160,7 +160,8 @@ abstract contract MultiSignerERC7913 is AbstractSigner {
      *
      * Requirements:
      *
-     * * The {signers}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.
+     * * The {getSignerCount} must be greater or equal than to the {threshold}. Throws
+     * {MultiSignerERC7913UnreachableThreshold} if not.
      */
     function _validateReachableThreshold() internal view virtual {
         uint256 signersLength = _signers.length();

+ 4 - 0
contracts/utils/cryptography/signers/SignerERC7913.sol

@@ -21,6 +21,10 @@ import {SignatureChecker} from "../SignatureChecker.sol";
  *     function initialize(bytes memory signer_) public initializer {
  *       _setSigner(signer_);
  *     }
+ *
+ *     function setSigner(bytes memory signer_) public onlyEntryPointOrSelf {
+ *       _setSigner(signer_);
+ *     }
  * }
  * ```
  *

+ 5 - 0
docs/modules/ROOT/nav.adoc

@@ -7,6 +7,11 @@
 
 * xref:access-control.adoc[Access Control]
 
+* xref:account-abstraction.adoc[Account Abstraction]
+** xref:accounts.adoc[Accounts]
+*** xref:eoa-delegation.adoc[EOA Delegation]
+*** xref:multisig.adoc[Multisig]
+
 * xref:tokens.adoc[Tokens]
 ** xref:erc20.adoc[ERC-20]
 *** xref:erc20-supply.adoc[Creating Supply]

+ 100 - 0
docs/modules/ROOT/pages/account-abstraction.adoc

@@ -0,0 +1,100 @@
+= Account Abstraction
+
+Unlike Externally Owned Accounts (EOAs), smart contracts may contain arbitrary verification logic based on authentication mechanisms different to Ethereum's native xref:api:utils.adoc#ECDSA[ECDSA] and have execution advantages such as batching or gas sponsorship. To leverage these properties of smart contracts, the community has widely adopted https://eips.ethereum.org/EIPS/eip-4337[ERC-4337], a standard to process user operations through an alternative mempool.
+
+The library provides multiple contracts for Account Abstraction following this standard as it enables more flexible and user-friendly interactions with applications. Account Abstraction use cases include wallets in novel contexts (e.g. embedded wallets), more granular configuration of accounts, and recovery mechanisms. 
+
+== ERC-4337 Overview
+
+The ERC-4337 is a detailed specification of how to implement the necessary logic to handle operations without making changes to the protocol level (i.e. the rules of the blockchain itself). This specification defines the following components:
+
+=== UserOperation
+
+A `UserOperation` is a higher-layer pseudo-transaction object that represents the intent of the account. This shares some similarities with regular EVM transactions like the concept of `gasFees` or `callData` but includes fields that enable new capabilities.
+
+```solidity
+struct PackedUserOperation {
+    address sender;
+    uint256 nonce;
+    bytes initCode; // concatenation of factory address and factoryData (or empty)
+    bytes callData;
+    bytes32 accountGasLimits; // concatenation of verificationGas (16 bytes) and callGas (16 bytes)
+    uint256 preVerificationGas;
+    bytes32 gasFees; // concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes)
+    bytes paymasterAndData; // concatenation of paymaster fields (or empty)
+    bytes signature;
+}
+```
+
+This process of bundling user operations involves several costs that the bundler must cover, including base transaction fees, calldata serialization, entrypoint execution, and paymaster context costs. To compensate for these expenses, bundlers use the `preVerificationGas` and `gasFees` fields to charge users appropriately.
+
+NOTE: Estimating `preVerificationGas` is not standardized as it varies based on network conditions such as gas prices and the size of the operation bundle.
+
+TIP: Use xref:api:account.adoc#ERC4337Utils[`ERC4337Utils`] to manipulate the `UserOperation` struct and other ERC-4337 related values.
+
+=== Entrypoint
+
+Each `UserOperation` is executed through a contract known as the https://etherscan.io/address/0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108#code[`EntryPoint`]. This contract is a singleton deployed across multiple networks at the same address although other custom implementations may be used.
+
+The Entrypoint contracts is considered a trusted entity by the account.
+
+=== Bundlers
+
+The bundler is a piece of _offchain_ infrastructure that is in charge of processing an alternative mempool of user operations. Bundlers themselves call the Entrypoint contract's `handleOps` function with an array of UserOperations that are executed and included in a block.
+
+During the process, the bundler pays for the gas of executing the transaction and gets refunded during the execution phase of the Entrypoint contract.
+
+```solidity
+/// @dev Process `userOps` and `beneficiary` receives all
+/// the gas fees collected during the bundle execution.
+function handleOps(
+    PackedUserOperation[] calldata ops,
+    address payable beneficiary
+) external { ... }
+```
+
+=== Account Contract
+
+The Account Contract is a smart contract that implements the logic required to validate a `UserOperation` in the context of ERC-4337. Any smart contract account should conform with the `IAccount` interface to validate operations.
+
+```solidity
+interface IAccount {
+    function validateUserOp(PackedUserOperation calldata, bytes32, uint256) external returns (uint256 validationData);
+}
+```
+
+Similarly, an Account should have a way to execute these operations by either handling arbitrary calldata on its `fallback` or implementing the `IAccountExecute` interface:
+
+```solidity
+interface IAccountExecute {
+    function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
+}
+```
+
+NOTE: The `IAccountExecute` interface is optional. Developers might want to use xref:api:account.adoc#ERC7821[`ERC-7821`] for a minimal batched execution interface or rely on ERC-7579 or any other execution logic.
+
+To build your own account, see xref:accounts.adoc[accounts].
+
+=== Factory Contract
+
+The smart contract accounts are created by a Factory contract defined by the Account developer. This factory receives arbitrary bytes as `initData` and returns an `address` where the logic of the account is deployed.
+
+To build your own factory, see xref:accounts.adoc#accounts_factory[account factories].
+
+=== Paymaster Contract
+
+A Paymaster is an optional entity that can sponsor gas fees for Accounts, or allow them to pay for those fees in ERC-20 instead of native currency. This abstracts gas away of the user experience in the same way that computational costs of cloud servers are abstracted away from end-users.
+
+To build your own paymaster, see https://docs.openzeppelin.com/community-contracts/0.0.1/paymasters[paymasters].
+
+== Further notes
+
+=== ERC-7562 Validation Rules
+
+To process a bundle of `UserOperations`, bundlers call xref:api:account.adoc#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-[`validateUserOp`] on each operation sender to check whether the operation can be executed. However, the bundler has no guarantee that the state of the blockchain will remain the same after the validation phase. To overcome this problem, https://eips.ethereum.org/EIPS/eip-7562[ERC-7562] proposes a set of limitations to EVM code so that bundlers (or node operators) are protected from unexpected state changes.
+
+These rules outline the requirements for operations to be processed by the canonical mempool.
+
+Accounts can access its own storage during the validation phase, they might easily violate ERC-7562 storage access rules in undirect ways. For example, most accounts access their public keys from storage when validating a signature, limiting the ability of having accounts that validate operations for other accounts (e.g. via ERC-1271)
+
+TIP: Although any Account that breaks such rules may still be processed by a private bundler, developers should keep in mind the centralization tradeoffs of relying on private infrastructure instead of _permissionless_ execution.

+ 354 - 0
docs/modules/ROOT/pages/accounts.adoc

@@ -0,0 +1,354 @@
+= Smart Accounts
+
+OpenZeppelin provides a simple xref:api:account.adoc#Account[`Account`] implementation including only the basic logic to handle user operations in compliance with ERC-4337. Developers who want to build their own account can leverage it to bootstrap custom implementations.
+
+User operations are validated using an xref:api:utils.adoc#AbstractSigner[`AbstractSigner`], which requires to implement the internal xref:api:utils.adoc#AbstractSigner-_rawSignatureValidation-bytes32-bytes-[`_rawSignatureValidation`] function, of which we offer a set of implementations to cover a wide customization range. This is the lowest-level signature validation layer and is used to wrap other validation methods like the Account's xref:api:account.adoc#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-[`validateUserOp`].
+
+== Setting up an account
+
+To setup an account, you can either start configuring it using our Wizard and selecting a predefined validation scheme, or bring your own logic and start by inheriting xref:api:account.adoc#Account[`Account`] from scratch. 
+
+++++
+<script async src="https://wizard.openzeppelin.com/build/embed.js"></script>
+
+<oz-wizard data-tab="Account" style="display: block; min-height: 40rem;"></oz-wizard>
+++++
+
+NOTE: Accounts don't support xref:erc721.adoc[ERC-721] and xref:erc1155.adoc[ERC-1155] tokens natively since these require the receiving address to implement an acceptance check. It is recommended to inherit xref:api:token/ERC721.adoc#ERC721Holder[ERC721Holder], xref:api:token/ERC1155.adoc#ERC1155Holder[ERC1155Holder] to include these checks in your account.
+
+=== Selecting a signer
+
+Since the minimum requirement of xref:api:account.adoc#Account[`Account`] is to provide an implementation of xref:api:utils/cryptography.adoc#AbstractSigner-_rawSignatureValidation-bytes32-bytes-[`_rawSignatureValidation`], the library includes specializations of the `AbstractSigner` contract that use custom digital signature verification algorithms. Some examples that you can select from include:
+
+* xref:api:utils/cryptography.adoc#SignerECDSA[`SignerECDSA`]: Verifies signatures produced by regular EVM Externally Owned Accounts (EOAs).
+* xref:api:utils/cryptography.adoc#SignerP256[`SignerP256`]: Validates signatures using the secp256r1 curve, common for World Wide Web Consortium (W3C) standards such as FIDO keys, passkeys or secure enclaves.
+* xref:api:utils/cryptography.adoc#SignerRSA[`SignerRSA`]: Verifies signatures of traditional PKI systems and X.509 certificates.
+* xref:api:utils/cryptography.adoc#SignerERC7702[`SignerERC7702`]: Checks EOA signatures delegated to this signer using https://eips.ethereum.org/EIPS/eip-7702#set-code-transaction[EIP-7702 authorizations]
+* xref:api:utils/cryptography.adoc#SignerERC7913[`SignerERC7913`]: Verifies generalized signatures following https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
+* https://docs.openzeppelin.com/community-contracts/0.0.1/api/utils#SignerZKEmail[`SignerZKEmail`]: Enables email-based authentication for smart contracts using zero knowledge proofs of email authority signatures.
+* xref:api:utils/cryptography.adoc#MultiSignerERC7913[`MultiSignerERC7913`]: Allows using multiple ERC-7913 signers with a threshold-based signature verification system.
+* xref:api:utils/cryptography.adoc#MultiSignerERC7913Weighted[`MultiSignerERC7913Weighted`]: Overrides the threshold mechanism of xref:api:utils/cryptography.adoc#MultiSignerERC7913[`MultiSignerERC7913`], offering different weights per signer.
+
+TIP: Given xref:api:utils/cryptography.adoc#SignerERC7913[`SignerERC7913`] provides a generalized standard for signature validation, you don't need to implement your own xref:api:utils/cryptography.adoc#AbstractSigner[`AbstractSigner`] for different signature schemes, consider bringing your own ERC-7913 verifier instead.
+
+==== Accounts factory
+
+The first time you send an user operation, your account will be created deterministically (i.e. its code and address can be predicted) using the the `initCode` field in the UserOperation. This field contains both the address of a smart contract (the factory) and the data required to call it and create your smart account.
+
+Suggestively, you can create your own account factory using the xref:api:proxy.adoc#Clones[Clones library], taking advantage of decreased deployment costs and account address predictability.
+
+[source,solidity]
+----
+include::api:example$account/MyFactoryAccount.sol[]
+----
+
+Account factories should be carefully implemented to ensure the account address is deterministically tied to the initial owners. This prevents frontrunning attacks where a malicious actor could deploy the account with their own owners before the intended owner does. The factory should include the owner's address in the salt used for address calculation.
+
+==== Handling initialization
+
+Most smart accounts are deployed by a factory, the best practice is to create xref:api:proxy.adoc#minimal_clones[minimal clones] of initializable contracts. These signer implementations provide an initializable design by default so that the factory can interact with the account to set it up right after deployment in a single transaction.
+
+[source,solidity]
+----
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {SignerECDSA} from "@openzeppelin/community-contracts/utils/cryptography/SignerECDSA.sol";
+
+contract MyAccount is Initializable, Account, SignerECDSA, ... {
+    // ...
+
+    function initializeECDSA(address signer) public initializer {
+        _setSigner(signer);
+    }
+}
+----
+
+Note that some account implementations may be deployed directly and therefore, won't require a factory.
+
+WARNING: Leaving an account uninitialized may leave it unusable since no public key was associated with it.
+
+=== Signature validation
+
+Regularly, accounts implement https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] to enable smart contract signature verification given its wide adoption. To be compliant means that smart contract exposes an xref:api:interfaces.adoc#IERC1271-isValidSignature-bytes32-bytes-[`isValidSignature(bytes32 hash, bytes memory signature)`] method that returns `0x1626ba7e` to identify whether the signature is valid.
+
+The benefit of this standard is that it allows to receive any format of `signature` for a given `hash`. This generalized mechanism fits very well with the account abstraction principle of _bringing your own validation mechanism_.
+
+This is how you enable ERC-1271 using an xref:api:utils/cryptography.adoc#AbstractSigner[`AbstractSigner`]:
+
+[source,solidity]
+----
+function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) {
+    return _rawSignatureValidation(hash, signature) ? IERC1271.isValidSignature.selector : bytes4(0xffffffff);
+}
+----
+
+IMPORTANT: We recommend using xref:api:utils/cryptography.adoc#ERC7739[ERC7739] to avoid replayability across accounts. This defensive rehashing mechanism that prevents signatures for this account to be replayed in another account controlled by the same signer. See xref:accounts.adoc#erc_7739_signatures[ERC-7739 signatures].
+
+=== Batched execution
+
+Batched execution allows accounts to execute multiple calls in a single transaction, which is particularly useful for bundling operations that need to be atomic. This is especially valuable in the context of account abstraction where you want to minimize the number of user operations and associated gas costs. xref:api:account.adoc#ERC7821[`ERC-7821`] standard provides a minimal interface for batched execution. 
+
+The library implementation supports a single batch mode (`0x01000000000000000000`) and allows accounts to execute multiple calls atomically. The standard includes access control through the xref:api:account.adoc#ERC7821-_erc7821AuthorizedExecutor-address-bytes32-bytes-[`_erc7821AuthorizedExecutor`] function, which by default only allows the contract itself to execute batches.
+
+Here's an example of how to use batched execution using EIP-7702:
+
+[source,solidity]
+----
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/draft-ERC7821.sol";
+import {SignerERC7702} from "@openzeppelin/community-contracts/utils/cryptography/SignerERC7702.sol";
+
+contract MyAccount is Account, SignerERC7702, ERC7821 {
+    // Override to allow the entrypoint to execute batches
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+The batched execution data follows a specific format that includes the calls to be executed. This format follows the same format as https://eips.ethereum.org/EIPS/eip-7579#execution-behavior[ERC-7579 execution] but only supports `0x01` call type (i.e. batched `call`) and default execution type (i.e. reverts if at least one subcall does).
+
+To encode an ERC-7821 batch, you can use https://viem.sh/[viem]'s utilities:
+
+[source,typescript]
+----
+// CALL_TYPE_BATCH, EXEC_TYPE_DEFAULT, ..., selector, payload 
+const mode = encodePacked(
+  ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"],
+  ["0x01", "0x00", "0x00000000", "0x00000000", "0x00000000000000000000000000000000000000000000"]
+);
+
+const entries = [
+  {
+    target: "0x000...0001",
+    value: 0n,
+    data: "0x000...000",
+  },
+  {
+    target: "0x000...0002",
+    value: 0n,
+    data: "0x000...000",
+  }
+];
+
+const batch = encodeAbiParameters(
+  [parseAbiParameter("(address,uint256,bytes)[]")],
+  [
+    entries.map<[Address, bigint, Hex]>((entry) =>
+      [entry.target, entry.value ?? 0n, entry.data ?? "0x"]
+    ),
+  ]
+);
+
+const userOpData = encodeFunctionData({
+    abi: account.abi,
+    functionName: "execute",
+    args: [mode, batch]
+});
+----
+
+== Bundle a `UserOperation`
+
+xref:account-abstraction.adoc#useroperation[UserOperations] are a powerful abstraction layer that enable more sophisticated transaction capabilities compared to traditional Ethereum transactions. To get started, you'll need to an account, which you can get by xref:accounts.adoc#accounts_factory[deploying a factory] for your implementation.
+
+=== Preparing a UserOp
+
+A UserOperation is a struct that contains all the necessary information for the EntryPoint to execute your transaction. You'll need the `sender`, `nonce`, `accountGasLimits` and `callData` fields to construct a `PackedUserOperation` that can be signed later (to populate the `signature` field).
+
+TIP: Specify `paymasterAndData` with the address of a paymaster contract concatenated to `data` that will be passed to the paymaster's validatePaymasterUserOp function to support sponsorship as part of your user operation.
+
+Here's how to prepare one using https://viem.sh/[viem]:
+
+[source,typescript]
+----
+import { getContract, createWalletClient, http, Hex } from 'viem';
+
+const walletClient = createWalletClient({
+  account, // See Viem's `privateKeyToAccount`
+  chain, // import { ... } from 'viem/chains';
+  transport: http(),
+})
+
+const entrypoint = getContract({
+  abi: [/* ENTRYPOINT ABI */],
+  address: '0x<ENTRYPOINT_ADDRESS>',
+  client: walletClient,
+});
+
+const userOp = {
+  sender: '0x<YOUR_ACCOUNT_ADDRESS>',
+  nonce: await entrypoint.read.getNonce([sender, 0n]),
+  initCode: "0x" as Hex,
+  callData: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
+  accountGasLimits: encodePacked(
+    ["uint128", "uint128"],
+    [
+      100_000n, // verificationGasLimit
+      300_000n, // callGasLimit
+    ]
+  ),
+  preVerificationGas: 50_000n,
+  gasFees: encodePacked(
+    ["uint128", "uint128"],
+    [
+      0n, // maxPriorityFeePerGas
+      0n, // maxFeePerGas
+    ]
+  ),
+  paymasterAndData: "0x" as Hex,
+  signature: "0x" as Hex,
+};
+----
+
+In case your account hasn't been deployed yet, make sure to provide the `initCode` field as `abi.encodePacked(factory, factoryData)` to deploy the account within the same UserOp:
+
+[source,typescript]
+----
+const deployed = await publicClient.getCode({ address: predictedAddress });
+
+if (!deployed) {
+  userOp.initCode = encodePacked(
+    ["address", "bytes"],
+    [
+      '0x<ACCOUNT_FACTORY_ADDRESS>',
+      encodeFunctionData({
+        abi: [/* ACCOUNT ABI */],
+        functionName: "<FUNCTION NAME>",
+        args: [...],
+      }),
+    ]
+  );
+}
+----
+
+==== Estimating gas
+
+To calculate gas parameters of a `UserOperation`, developers should carefully consider the following fields:
+
+* `verificationGasLimit`: This covers the gas costs for signature verification, paymaster validation (if used), and account validation logic. While a typical value is around 100,000 gas units, this can vary significantly based on the complexity of your signature validation scheme in both the account and paymaster contracts.
+
+* `callGasLimit`: This parameter accounts for the actual execution of your account's logic. It's recommended to use `eth_estimateGas` for each subcall and add additional buffer for computational overhead.
+
+* `preVerificationGas`: This compensates for the EntryPoint's execution overhead. While 50,000 gas is a reasonable starting point, you may need to increase this value based on your UserOperation's size and specific bundler requirements.
+
+NOTE: The `maxFeePerGas` and `maxPriorityFeePerGas` values are typically provided by your bundler service, either through their SDK or a custom RPC method.
+
+IMPORTANT: A penalty of 10% (`UNUSED_GAS_PENALTY_PERCENT`) is applied on the amounts of `callGasLimit` and `paymasterPostOpGasLimit` gas that remains unused if the amount of remaining unused gas is greater than or equal to 40,000 (`PENALTY_GAS_THRESHOLD`).
+
+=== Signing the UserOp
+
+To sign a UserOperation, you'll need to first calculate its hash as an EIP-712 typed data structure using the EntryPoint's domain, then sign this hash using your account's signature scheme, and finally encode the resulting signature in the format that your account contract expects for verification.
+
+[source,typescript]
+----
+import { signTypedData } from 'viem/actions';
+
+// EntryPoint v0.8 EIP-712 domain
+const domain = {
+  name: 'ERC4337',
+  version: '1',
+  chainId: 1, // Your target chain ID
+  verifyingContract: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', // v08
+};
+
+// EIP-712 types for PackedUserOperation
+const types = {
+  PackedUserOperation: [
+    { name: 'sender', type: 'address' },
+    { name: 'nonce', type: 'uint256' },
+    { name: 'initCode', type: 'bytes' },
+    { name: 'callData', type: 'bytes' },
+    { name: 'accountGasLimits', type: 'bytes32' },
+    { name: 'preVerificationGas', type: 'uint256' },
+    { name: 'gasFees', type: 'bytes32' },
+    { name: 'paymasterAndData', type: 'bytes' },
+  ],
+} as const;
+
+// Sign the UserOperation using EIP-712
+userOp.signature = await eoa.signTypedData({
+  domain,
+  types,
+  primaryType: 'PackedUserOperation',
+  message: {
+    sender: userOp.sender,
+    nonce: userOp.nonce,
+    initCode: userOp.initCode,
+    callData: userOp.callData,
+    accountGasLimits: userOp.accountGasLimits,
+    preVerificationGas: userOp.preVerificationGas,
+    gasFees: userOp.gasFees,
+    paymasterAndData: userOp.paymasterAndData,
+  },
+});
+----
+
+Alternatively, developers can get the raw user operation hash by using the Entrypoint's `getUserOpHash` function:
+
+[source,typescript]
+----
+const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
+userOp.signature = await eoa.sign({ hash: userOpHash });
+----
+
+IMPORTANT: Using `getUserOpHash` directly may provide a poorer user experience as users see an opaque hash rather than structured transaction data. In many cases, offchain signers won't have an option to sign a raw hash.
+
+=== Sending the UserOp
+
+Finally, to send the user operation you can call `handleOps` on the Entrypoint contract and set yourself as the `beneficiary`. 
+
+[source,typescript]
+----
+// Send the UserOperation
+const userOpReceipt = await walletClient
+  .writeContract({
+    abi: [/* ENTRYPOINT ABI */],
+    address: '0x<ENTRYPOINT_ADDRESS>',
+    functionName: "handleOps",
+    args: [[userOp], eoa.address],
+  })
+  .then((txHash) =>
+    publicClient.waitForTransactionReceipt({
+      hash: txHash,
+    })
+  );
+
+// Print receipt
+console.log(userOpReceipt);
+----
+
+TIP: Since you're bundling your user operations yourself, you can safely specify `preVerificationGas` and `maxFeePerGas` in 0.
+
+=== Using a Bundler
+
+For better reliability, consider using a bundler service. Bundlers provide several key benefits: they automatically handle gas estimation, manage transaction ordering, support bundling multiple operations together, and generally offer higher transaction success rates compared to self-bundling.
+
+== Further notes
+
+=== ERC-7739 Signatures
+
+A common security practice to prevent user operation https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU[replayability across smart contract accounts controlled by the same private key] (i.e. multiple accounts for the same signer) is to link the signature to the `address` and `chainId` of the account. This can be done by asking the user to sign a hash that includes these values.
+
+The problem with this approach is that the user might be prompted by the wallet provider to sign an https://x.com/howydev/status/1780353754333634738[obfuscated message], which is a phishing vector that may lead to a user losing its assets.
+
+To prevent this, developers may use xref:api:account#ERC7739Signer[`ERC7739Signer`], a utility that implements xref:api:interfaces#IERC1271[`IERC1271`] for smart contract signatures with a defensive rehashing mechanism based on a https://github.com/frangio/eip712-wrapper-for-eip1271[nested EIP-712 approach] to wrap the signature request in a context where there's clearer information for the end user.
+
+=== EIP-7702 Delegation
+
+https://eips.ethereum.org/EIPS/eip-7702[EIP-7702] lets EOAs delegate to smart contracts while keeping their original signing key. This creates a hybrid account that works like an EOA for signing but has smart contract features. Protocols don't need major changes to support EIP-7702 since they already handle both EOAs and smart contracts (see xref:api:utils/cryptography.adoc#SignatureChecker[SignatureChecker]).
+
+The signature verification stays compatible: delegated EOAs are treated as contracts using ERC-1271, making it easy to redelegate to a contract with ERC-1271 support with little overhead by reusing the validation mechanism of the account. 
+
+TIP: Learn more about delegating to an ERC-7702 account in our xref:eoa-delegation.adoc[EOA Delegation] section.
+
+=== ERC-7579 Modules
+
+Smart accounts have evolved to embrace modularity as a design principle, with popular implementations like https://erc7579.com/#supporters[Safe, Pimlico, Rhinestone, Etherspot and many others] agreeing on ERC-7579 as the standard for module interoperability. This standardization enables accounts to extend their functionality through external contracts while maintaining compatibility across different implementations.
+
+OpenZeppelin Contracts provides both the building blocks for creating ERC-7579-compliant modules and an xref:api:account.adoc#AccountERC7579[`AccountERC7579`] implementation that supports installing and managing these modules. 
+
+TIP: Learn more in our https://docs.openzeppelin.com/community-contracts/0.0.1/account-modules[account modules] section.

+ 143 - 0
docs/modules/ROOT/pages/eoa-delegation.adoc

@@ -0,0 +1,143 @@
+= EOA Delegation
+
+https://eips.ethereum.org/EIPS/eip-7702[EIP-7702] introduces a new transaction type (`0x4`) that grants https://ethereum.org/en/developers/docs/accounts/[Externally Owned Accounts (EOAs)] the ability to delegate execution to an smart contract. This is particularly useful to enable traditional EVM accounts to:
+
+* Batch multiple operations in a single transaction (e.g. xref:api:token/ERC20.adoc#IERC20-approve-address-uint256-[`approve`] + xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[`transfer`], yey!)
+* Sponsoring transactions for other users.
+* Implementing privilege de-escalation (e.g., sub-keys with limited permissions)
+
+This section walks you through the process of delegating an EOA to a contract following https://eips.ethereum.org/EIPS/eip-7702[ERC-7702]. This allows you to use your EOA's private key to sign and execute operations with custom execution logic. Combined with https://eips.ethereum.org/EIPS/eip-4337[ERC-4337] infrastructure, users can achieve gas sponsoring through https://docs.openzeppelin.com/community-contracts/paymasters[paymasters].
+
+== Delegating execution
+
+EIP-7702 enables EOAs to delegate their execution capabilities to smart contracts, effectively bridging the gap between traditional and xref:accounts.adoc[Smart Accounts]. The xref:api:utils/cryptography.adoc#[`SignerERC7702`] utility facilitates this delegation by verifying signatures against the EOA's address (`address(this)`), making it easier to implement EIP-7702 in smart contract accounts.
+
+[source,solidity]
+----
+include::api:example$account/MyAccountERC7702.sol[]
+----
+
+TIP: Users can delegate to an instance of xref:api:account.adoc#ERC7821[`ERC-7821`] for a minimal batch executor that does not use ERC-4337 related code.
+
+=== Signing Authorization
+
+To authorize delegation, the EOA owner signs a message containing the chain ID, nonce, delegation address, and signature components (i.e. `[chain_id, address, nonce, y_parity, r, s]`). This signed authorization serves two purposes: it restricts execution to only the delegate contract and prevents replay attacks.
+
+The EOA maintains a delegation designator for each authorized address on each chain, which points to the contract whose code will be executed in the EOA's context to handle delegated operations.
+
+Here's how to construct an authorization with https://viem.sh/[viem]:
+
+[source, typescript]
+----
+// Remember not to hardcode your private key! 
+const eoa = privateKeyToAccount('<YOUR_PRIVATE_KEY>');
+const eoaClient = createWalletClient({
+  account: eoa,
+  chain: publicClient.chain,
+  transport: http(),
+});
+
+const walletClient = createWalletClient({
+  account, // See Viem's `privateKeyToAccount`
+  chain, // import { ... } from 'viem/chains';
+  transport: http(),
+})
+
+const authorization = await eoaClient.signAuthorization({
+  account: walletClient.account.address,
+  contractAddress: '0x<YOUR_DELEGATE_CONTRACT_ADDRESS>',
+  // Use instead of `account` if your 
+  // walletClient's account is the one sending the transaction
+  // executor: "self",
+});
+----
+
+NOTE: When implementing delegate contracts, ensure they require signatures that avoid replayability (e.g. a domain separator, nonce).
+A poorly implemented delegate can allow a malicious actor to take near complete control over a signer's EOA.
+
+=== Send a Set Code Transaction
+
+After signing the authorization, you need to send a `SET_CODE_TX_TYPE` (0x04) transaction to write the delegation designator (i.e. `0xef0100 || address`) to your EOA's code, which tells the EVM to load and execute code from the specified address when operations are performed on your EOA.
+
+[source, typescript]
+----
+// Send the `authorization` along with `data`
+const receipt = await walletClient
+  .sendTransaction({
+    authorizationList: [authorization],
+    data: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
+    to: eoa.address,
+  })
+  .then((txHash) =>
+    publicClient.waitForTransactionReceipt({
+      hash: txHash,
+    })
+  );
+
+// Print receipt
+console.log(userOpReceipt);
+----
+
+To remove the delegation and restore your EOA to its original state, you can send a `SET_CODE_TX_TYPE` transaction with an authorization tuple that points to the zero address (`0x0000000000000000000000000000000000000000`). This will clear the account's code and reset its code hash to the empty hash, however, be aware that it will not automatically clean the EOA storage.
+
+When changing an account's delegation, ensure the newly delegated code is purposely designed and tested as an upgrade to the old one. To ensure safe migration between delegate contracts, namespaced storage that avoid accidental collisions following ERC-7201. 
+
+WARNING: Updating the delegation designator may render your EOA unusable due to potential storage collisions. We recommend following similar practices to those of https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable[writing upgradeable smart contracts].
+
+== Using with ERC-4337
+
+The ability to set code to execute logic on an EOA allows users to leverage ERC-4337 infrastructure to process user operations. Developers only need to combine an xref:api:account.adoc#Account[`Account`] contract with an xref:api:utils/cryptography.adoc#SignerERC7702[`SignerERC7702`] to accomplish ERC-4337 compliance out of the box.
+
+=== Sending a UserOp
+
+Once your EOA is delegated to an ERC-4337 compatible account, you can send user operations through the entry point contract. The user operation includes all the necessary fields for execution, including gas limits, fees, and the actual call data to execute. The entry point will validate the operation and execute it in the context of your delegated account.
+
+Similar to how xref:accounts.adoc#bundle_a_useroperation[sending a UserOp] is achieved for factory accounts, here's how you can construct a UserOp for an EOA who's delegated to an xref:api:account.adoc#Account[`Account`] contract.
+
+[source, typescript]
+----
+const userOp = {
+  sender: eoa.address,
+  nonce: await entrypoint.read.getNonce([eoa.address, 0n]),
+  initCode: "0x" as Hex,
+  callData: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
+  accountGasLimits: encodePacked(
+    ["uint128", "uint128"],
+    [
+      100_000n, // verificationGas
+      300_000n, // callGas
+    ]
+  ),
+  preVerificationGas: 50_000n,
+  gasFees: encodePacked(
+    ["uint128", "uint128"],
+    [
+      0n, // maxPriorityFeePerGas
+      0n, // maxFeePerGas
+    ]
+  ),
+  paymasterAndData: "0x" as Hex,
+  signature: "0x" as Hex,
+};
+
+const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
+userOp.signature = await eoa.sign({ hash: userOpHash });
+
+const userOpReceipt = await eoaClient
+  .writeContract({
+    abi: EntrypointV08Abi,
+    address: entrypoint.address,
+    authorizationList: [authorization],
+    functionName: "handleOps",
+    args: [[userOp], eoa.address],
+  })
+  .then((txHash) =>
+    publicClient.waitForTransactionReceipt({
+      hash: txHash,
+    })
+  );
+
+console.log(userOpReceipt);
+----
+
+NOTE: When using sponsored transaction relayers, be aware that the authorized account can cause relayers to spend gas without being reimbursed by either invalidating the authorization (increasing the account's nonce) or by sweeping the relevant assets out of the account. Relayers may implement safeguards like requiring a bond or using a reputation system.

+ 306 - 0
docs/modules/ROOT/pages/multisig.adoc

@@ -0,0 +1,306 @@
+= Multisig Account
+
+A multi-signature (multisig) account is a smart account that requires multiple authorized signers to approve operations before execution. Unlike traditional accounts controlled by a single private key, multisigs distribute control among multiple parties, eliminating single points of failure. For example, a 2-of-3 multisig requires signatures from at least 2 out of 3 possible signers.
+
+Popular implementations like https://safe.global/[Safe] (formerly Gnosis Safe) have become the standard for securing valuable assets. Multisigs provide enhanced security through collective authorization, customizable controls for ownership and thresholds, and the ability to rotate signers without changing the account address.
+
+== Beyond Standard Signature Verification
+
+As discussed in the xref:accounts.adoc#signature_validation[accounts section], the standard approach for smart contracts to verify signatures is https://eips.ethereum.org/EIPS/eip-1271[ERC-1271], which defines an `isValidSignature(hash, signature)`. However, it is limited in two important ways:
+
+1. It assumes the signer has an EVM address
+2. It treats the signer as a single identity
+
+This becomes problematic when implementing multisig accounts where:
+
+* You may want to use signers that don't have EVM addresses (like keys from hardware devices)
+* Each signer needs to be individually verified rather than treated as a collective identity
+* You need a threshold system to determine when enough valid signatures are present
+
+The https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol[SignatureChecker] library is useful for verifying EOA and ERC-1271 signatures, but it's not designed for more complex arrangements like threshold-based multisigs.
+
+== ERC-7913 Signers
+
+https://eips.ethereum.org/EIPS/eip-7913[ERC-7913] extends the concept of signer representation to include keys that don't have EVM addresses, addressing this limitation. OpenZeppelin implements this standard through three contracts:
+
+=== SignerERC7913
+
+The xref:api:utils.adoc#SignerERC7913[`SignerERC7913`] contract allows a single ERC-7913 formatted signer to control an account. The signer is represented as a `bytes` object that concatenates a verifier address and a key: `verifier || key`.
+
+[source,solidity]
+----
+// contracts/MyAccountERC7913.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.24;
+
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {SignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7913.sol";
+
+contract MyAccountERC7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
+    constructor() EIP712("MyAccount7913", "1") {}
+
+    function initialize(bytes memory signer) public initializer {
+        _setSigner(signer);
+    }
+
+    function setSigner(bytes memory signer) public onlyEntryPointOrSelf {
+        _setSigner(signer);
+    }
+
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+WARNING: Leaving an account uninitialized may leave it unusable since no public key was associated with it.
+
+=== MultiSignerERC7913
+
+The xref:api:utils/cryptography.adoc#MultiSignerERC7913[`MultiSignerERC7913`] contract extends this concept to support multiple signers with a threshold-based signature verification system.
+
+[source,solidity]
+----
+// contracts/MyAccountMultiSigner.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.27;
+
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {MultiSignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913.sol";
+
+contract MyAccountMultiSigner is
+    Account,
+    MultiSignerERC7913,
+    ERC7739,
+    ERC7821,
+    ERC721Holder,
+    ERC1155Holder,
+    Initializable
+{
+    constructor() EIP712("MyAccountMultiSigner", "1") {}
+
+    function initialize(bytes[] memory signers, uint256 threshold) public initializer {
+        _addSigners(signers);
+        _setThreshold(threshold);
+    }
+
+    function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _addSigners(signers);
+    }
+
+    function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _removeSigners(signers);
+    }
+
+    function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
+        _setThreshold(threshold);
+    }
+
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+This implementation is ideal for standard multisig setups where each signer has equal authority, and a fixed number of approvals is required.
+
+The `MultiSignerERC7913` contract provides several key features for managing multi-signature accounts. It maintains a set of authorized signers and implements a threshold-based system that requires a minimum number of signatures to approve operations. The contract includes an internal interface for managing signers, allowing for the addition and removal of authorized parties.
+
+NOTE: `MultiSignerERC7913` safeguards to ensure that the threshold remains achievable based on the current number of active signers, preventing situations where operations could become impossible to execute.
+
+The contract also provides public functions for querying signer information: xref:api:utils/cryptography.adoc#MultiSignerERC7913-isSigner-bytes-[`isSigner(bytes memory signer)`] to check if a given signer is authorized, xref:api:utils/cryptography.adoc#MultiSignerERC7913-getSigners-uint64-uint64-[`getSigners(uint64 start, uint64 end)`] to retrieve a paginated list of authorized signers, and xref:api:utils/cryptography.adoc#MultiSignerERC7913-getSignerCount[`getSignerCount()`] to get the total number of signers. These functions are useful when validating signatures, implementing customized access control logic, or building user interfaces that need to display signer information.
+
+=== MultiSignerERC7913Weighted
+
+For more sophisticated governance structures, the xref:api:utils/cryptography.adoc#MultiSignerERC7913Weighted[`MultiSignerERC7913Weighted`] contract extends `MultiSignerERC7913` by assigning different weights to each signer.
+
+[source,solidity]
+----
+// contracts/MyAccountMultiSignerWeighted.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.27;
+
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {MultiSignerERC7913Weighted} from "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol";
+
+contract MyAccountMultiSignerWeighted is
+    Account,
+    MultiSignerERC7913Weighted,
+    ERC7739,
+    ERC7821,
+    ERC721Holder,
+    ERC1155Holder,
+    Initializable
+{
+    constructor() EIP712("MyAccountMultiSignerWeighted", "1") {}
+
+    function initialize(bytes[] memory signers, uint256[] memory weights, uint256 threshold) public initializer {
+        _addSigners(signers);
+        _setSignerWeights(signers, weights);
+        _setThreshold(threshold);
+    }
+
+    function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _addSigners(signers);
+    }
+
+    function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _removeSigners(signers);
+    }
+
+    function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
+        _setThreshold(threshold);
+    }
+
+    function setSignerWeights(bytes[] memory signers, uint256[] memory weights) public onlyEntryPointOrSelf {
+        _setSignerWeights(signers, weights);
+    }
+
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+This implementation is perfect for scenarios where different signers should have varying levels of authority, such as:
+
+* Board members with different voting powers
+* Organizational structures with hierarchical decision-making
+* Hybrid governance systems combining core team and community members
+* Execution setups like "social recovery" where you trust particular guardians more than others
+
+The `MultiSignerERC7913Weighted` contract extends `MultiSignerERC7913` with a weighting system. Each signer can have a custom weight, and operations require the total weight of signing participants to meet or exceed the threshold. Signers without explicit weights default to a weight of 1.
+
+NOTE: When setting up a weighted multisig, ensure the threshold value matches the scale used for signer weights. For example, if signers have weights like 1, 2, or 3, then a threshold of 4 would require at least two signers (e.g., one with weight 1 and one with weight 3).
+
+== Setting Up a Multisig Account
+
+To create a multisig account, you need to:
+
+1. Define your signers
+2. Determine your threshold
+3. Initialize your account with these parameters
+
+The example below demonstrates setting up a 2-of-3 multisig account with different types of signers:
+
+[source,solidity]
+----
+// Example setup code
+function setupMultisigAccount() external {
+    // Create signers using different types of keys
+    bytes memory ecdsaSigner = alice; // EOA address (20 bytes)
+    
+    // P256 signer with format: verifier || pubKey
+    bytes memory p256Signer = abi.encodePacked(
+        p256Verifier,
+        bobP256PublicKeyX,
+        bobP256PublicKeyY
+    );
+    
+    // RSA signer with format: verifier || pubKey
+    bytes memory rsaSigner = abi.encodePacked(
+        rsaVerifier,
+        abi.encode(charlieRSAPublicKeyE, charlieRSAPublicKeyN)
+    );
+    
+    // Create array of signers
+    bytes[] memory signers = new bytes[](3);
+    signers[0] = ecdsaSigner;
+    signers[1] = p256Signer;
+    signers[2] = rsaSigner;
+    
+    // Set threshold to 2 (2-of-3 multisig)
+    uint256 threshold = 2;
+    
+    // Initialize the account
+    myMultisigAccount.initialize(signers, threshold);
+}
+----
+
+For a weighted multisig, you would also specify weights:
+
+[source,solidity]
+----
+// Example setup for weighted multisig
+function setupWeightedMultisigAccount() external {
+    // Create array of signers (same as above)
+    bytes[] memory signers = new bytes[](3);
+    signers[0] = ecdsaSigner;
+    signers[1] = p256Signer;
+    signers[2] = rsaSigner;
+    
+    // Assign weights to signers (Alice:1, Bob:2, Charlie:3)
+    uint256[] memory weights = new uint256[](3);
+    weights[0] = 1;
+    weights[1] = 2;
+    weights[2] = 3;
+    
+    // Set threshold to 4 (requires at least Bob+Charlie or all three)
+    uint256 threshold = 4;
+    
+    // Initialize the weighted account
+    myWeightedMultisigAccount.initialize(signers, weights, threshold);
+}
+----
+
+IMPORTANT: The xref:api:utils/cryptography.adoc#MultiSignerERC7913-_validateReachableThreshold--[`_validateReachableThreshold`] function ensures that the sum of weights for all active signers meets or exceeds the threshold. Any customization built on top of the multisigner contracts must ensure the threshold is always reachable.
+
+For multisig accounts, the signature is a complex structure that contains both the signers and their individual signatures. The format follows ERC-7913's specification and must be properly encoded.
+
+=== Signature Format
+
+The multisig signature is encoded as:
+
+[source,solidity]
+----
+abi.encode(
+    bytes[] signers,   // Array of signers sorted by `keccak256`
+    bytes[] signatures // Array of signatures corresponding to each signer
+)
+----
+
+Where:
+
+* `signers` is an array of the signers participating in this particular signature
+* `signatures` is an array of the individual signatures corresponding to each signer
+
+[NOTE]
+====
+To avoid duplicate signers, the contract uses `keccak256` to generate a unique id for each signer. When providing a multisignature, the `signers` array should be sorted in ascending order by `keccak256`, and the `signatures` array must match the order of their corresponding signers.
+====

+ 1 - 1
docs/modules/ROOT/pages/utilities.adoc

@@ -99,7 +99,7 @@ IMPORTANT: Always use keys of at least 2048 bits. Additionally, be aware that PK
 
 === Signature Verification
 
-The xref:api:utils.adoc#SignatureChecker[`SignatureChecker`] library provides a unified interface for verifying signatures from different sources. It seamlessly supports:
+The xref:api:utils/cryptography.adoc#SignatureChecker[`SignatureChecker`] library provides a unified interface for verifying signatures from different sources. It seamlessly supports:
 
 * ECDSA signatures from externally owned accounts (EOAs)
 * ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet