openzeppelin-contracts Documentation

OpenZeppelin Contracts is a library for secure smart contract development.

27,288 stars Solidity Solidity 1 file · ~56,782 tokens #ethereum#evm#security#smart-contracts#solidity
RAW Doc

Technical Documentation: OpenZeppelin/openzeppelin-contracts

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)





A library for secure smart contract development. Build on a solid foundation of community-vetted code.

:mage: Not sure how to get started? Check out Contracts Wizard — an interactive smart contract generator.

[!IMPORTANT]
OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. For upgradeable contracts, the storage layout of different major versions should be assumed incompatible, for example, it is unsafe to upgrade from 4.9.3 to 5.0.0. Learn more at Backwards Compatibility.

Overview

Release tags

We use NPM tags to clearly distinguish between audited and non-audited versions of our package:

Tag Purpose Description
latest ✅ Audited releases Stable, audited versions of the package. This is the default version installed when users run npm install @openzeppelin/contracts.
dev 🧪 Final but not audited Versions that are finalized and feature-complete but have not yet been audited. This version is fully tested, can be used in production and is covered by the bug bounty.
next 🚧 Release candidates Pre-release versions that are not final. Used for testing and validation before the version becomes a final dev or latest release.

Installation

Hardhat (npm)

text
$ npm install @openzeppelin/contracts

→ Installs the latest audited release (latest).

text
$ npm install @openzeppelin/contracts@dev

→ Installs the latest unaudited release (dev).

Foundry (git)

[!WARNING]
When installing via git, it is a common error to use the master branch. This is a development branch that should be avoided in favor of tagged releases. The release process involves security measures that the master branch does not guarantee.

[!WARNING]
Foundry installs the latest version initially, but subsequent forge update commands will use the master branch.

text
$ forge install OpenZeppelin/openzeppelin-contracts

Add @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ in remappings.txt.

Usage

Once installed, you can use the contracts in the library by importing them:

solidity
pragma solidity ^0.8.20;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyCollectible is ERC721 {
    constructor() ERC721("MyCollectible", "MCO") {
    }
}

If you're new to smart contract development, head to Developing Smart Contracts to learn about creating a new project and compiling your contracts.

To keep your system secure, you should always use the installed code as-is, and neither copy-paste it from online sources nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs.

Learn More

The guides in the documentation site will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides:

  • Access Control: decide who can perform each of the actions on your system.
  • Tokens: create tradeable assets or collectibles for popular ERC standards like ERC-20, ERC-721, ERC-1155, and ERC-6909.
  • Utilities: generic useful tools including non-overflowing math, signature verification, and trustless paying systems.

The full API is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the community forum.

Finally, you may want to take a look at the guides on our blog, which cover several common use cases and good practices. The following articles provide great background reading, though please note that some of the referenced tools have changed, as the tooling in the ecosystem continues to rapidly evolve.

Security

This project is maintained by OpenZeppelin with the goal of providing a secure and reliable library of smart contract components for the ecosystem. We address security through risk management in various areas such as engineering and open source best practices, scoping and API design, multi-layered review processes, and incident response preparedness.

The OpenZeppelin Contracts Security Center contains more details about the secure development process.

The security policy is detailed in SECURITY.md as well, and specifies how you can report security vulnerabilities, which versions will receive security patches, and how to stay informed about them. We run a bug bounty program on Immunefi to reward the responsible disclosure of vulnerabilities.

The engineering guidelines we follow to promote project quality can be found in GUIDELINES.md.

Past audits can be found in audits/.

Smart contracts are a nascent technology and carry a high level of technical risk and uncertainty. Although OpenZeppelin is well known for its security audits, using OpenZeppelin Contracts is not a substitute for a security audit.

OpenZeppelin Contracts is made available under the MIT License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including OpenZeppelin. As set out further in the Terms, you acknowledge that you are solely responsible for any use of OpenZeppelin Contracts and you assume all risks associated with any such use.

Contribute

OpenZeppelin Contracts exists thanks to its contributors. There are many ways you can participate and help build high quality software. Check out the contribution guide!

License

OpenZeppelin Contracts is released under the MIT License.

Your use of this Project is governed by the terms found at www.openzeppelin.com/tos (the "Terms").


Overview (`audits/README.md`)

Audits

Date Version Commit Auditor Scope Links
February 2026 v5.6.0 68e4095 OpenZeppelin v5.5 & v5.6 Changes 🔗
November 2025 v5.5.0 d9f966f OpenZeppelin RLP 🔗
October 2025 v5.5.0 f5edfc0 OpenZeppelin SafeERC20, ECDSA, SignatureChecker 🔗
July 2025 v5.4.0 f6fea85 OpenZeppelin v5.4 Changes 🔗
April 2025 v5.3.0 d4b2e98 OpenZeppelin v5.3 Changes 🔗
December 2024 v5.2.0 98d28f9 OpenZeppelin v5.2 Changes 🔗
October 2024 v5.1.0 aba9ff6 OpenZeppelin v5.1 Changes 🔗
October 2023 v5.0.0 b5a3e69 OpenZeppelin v5.0 Changes 🔗
May 2023 v4.9.0 91df66c OpenZeppelin v4.9 Changes 🔗
October 2022 v4.8.0 14f98db OpenZeppelin ERC4626, Checkpoints 🔗 🔗
October 2018 v2.0.0 dac5bcc LevelK Everything 🔗
March 2017 v1.0.4 9c5975a New Alchemy Everything 🔗

Formal Verification

Date Version Commit Tool Scope Links
May 2022 v4.7.0 109778c Certora Initializable, GovernorPreventLateQuorum, ERC1155Burnable, ERC1155Pausable, ERC1155Supply, ERC1155Holder, ERC1155Receiver 🔗
March 2022 v4.4.0 4088540 Certora ERC20Votes, ERC20FlashMint, ERC20Wrapper, TimelockController, ERC721Votes, Votes, AccessControl, ERC1155 🔗
October 2021 v4.4.0 4088540 Certora Governor, GovernorCountingSimple, GovernorProposalThreshold, GovernorTimelockControl, GovernorVotes, GovernorVotesQuorumFraction 🔗

Overview (`docs/README.md`)

Documentation is hosted at https://docs.openzeppelin.com/contracts.

All of the content for the site is in this repository. The guides are in the
docs directory, and the API Reference is extracted from comments in
the source code. If you want to help improve the content, this is the
repository you should be contributing to.

solidity-docgen is the
program that extracts the API Reference from source code.

The docs.openzeppelin.com
repository hosts the configuration for the entire site, which includes
documentation for all of the OpenZeppelin projects.

To run the docs locally you should run npm run docs:watch on this
repository.


Overview (`fv/README.md`)

Running formal verification tool

These instructions detail the process for running Formal Verification Tool on OpenZeppelin Contracts.

Documentation for CVT and the specification language is available here.

Prerequisites

Follow the Certora installation guide in order to get the Certora Prover Package and the solc executable folder in your path.

Note
An API Key is required for local testing. In CI the prover runs on any pull request whose diff reaches a spec, a harness, a contract one of them verifies, or a patch in fv/diff applied to one, with one job per affected configuration. That selection is computed by scripts/list-dependencies.js.

Running the verification

The Formal Verification Tool proves specs for contracts, which are defined by per-spec .conf files located under fv/specs/ along with their pre-configured options.

The verification script ./run.js is used to submit verification jobs to the Certora Verification service.

You can run it from the root of the repository with the following command:

bash
node fv/run.js [SPEC_NAME | fv/specs/NAME.conf] [--all] [-p N] [-v]

Where:

  • SPEC_NAME is the base name of a configuration file in fv/specs/ without extension. For example, AccessControl maps to fv/specs/AccessControl.conf.
  • Alternatively, you may pass an explicit path to a .conf file under fv/specs/.
  • Supported script options are --all, --parallel/-p, and --verbose/-v.

Note
A single spec may be configured to run for multiple contracts, whereas a single contract may run multiple specs.

Example usage:

bash
node fv/run.js AccessControl # Run the AccessControl configuration (fv/specs/AccessControl.conf) using its harness and spec

Adapting to changes in the contracts

Some of our rules require the code to be simplified in various ways. Our primary tool for performing these simplifications is to run verification on a contract that extends the original contracts and overrides some of the methods. These "harness" contracts can be found in the fv/harnesses directory.

This pattern does require some modifications to the original code: some methods need to be made virtual or public, for example. These changes are handled by applying a patch
to the code before verification by running:

bash
make -C fv apply

Before running the fv/run.js script, it's required to apply the corresponding patches to the contracts directory, placing the output in the fv/patched directory. Then, the contracts are verified by running the verification for the fv/patched directory.

If the original contracts change, it is possible to create a conflict with the patch. In this case, the verify scripts will report an error message and output rejected changes in the patched directory. After merging the changes, run make record in the fv directory; this will regenerate the patch file, which can then be checked into git.

For more information about the make scripts available, run:

bash
make -C fv help

Overview (`scripts/upgradeable/README.md`)

The upgradeable variant of OpenZeppelin Contracts is automatically generated from the original Solidity code. We call this process "transpilation" and it is implemented by our Upgradeability Transpiler.

When the master branch or release-v* branches are updated, the code is transpiled and pushed to OpenZeppelin/openzeppelin-contracts-upgradeable by the upgradeable.yml workflow.

`transpile.sh`

Applies patches and invokes the transpiler with the command line flags we need for our requirements (for example, excluding certain files).

`transpile-onto.sh`

text
bash scripts/upgradeable/transpile-onto.sh <target> [<base>]

Transpiles the contents of the current git branch and commits the result as a new commit on branch <target>. If branch <target> doesn't exist, it will copy the commit history of [] (this is used in GitHub Actions, but is usually not necessary locally).

`patch-apply.sh` & `patch-save.sh`

Some of the upgradeable contract variants require ad-hoc changes that are not implemented by the transpiler. These changes are implemented by patches stored in upgradeable.patch in this directory. patch-apply.sh applies these patches.

If the patches fail to apply due to changes in the repo, the conflicts have to be resolved manually. Once fixed, patch-save.sh will take the changes staged in Git and update upgradeable.patch to match.


Antora.Yml (`docs/antora.yml`)

name: contracts
title: Contracts
version: 5.x
prerelease: false
nav:

  • modules/ROOT/nav.adoc
  • modules/api/nav.adoc

Modules ROOT Nav (`docs/modules/ROOT/nav.adoc`)

  • xref:index.adoc[Overview]

  • xref:wizard.adoc[Wizard]

  • xref:extending-contracts.adoc[Extending Contracts]

  • xref:upgradeable.adoc[Using with Upgrades]

  • xref:backwards-compatibility.adoc[Backwards Compatibility]

  • 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:paymasters.adoc[Paymasters]

  • xref:tokens.adoc[Tokens]
    ** xref:erc20.adoc[ERC-20]
    *** xref:erc20-supply.adoc[Creating Supply]
    ** xref:erc721.adoc[ERC-721]
    ** xref:erc1155.adoc[ERC-1155]
    ** xref:erc4626.adoc[ERC-4626]
    ** xref:erc6909.adoc[ERC-6909]

  • xref:governance.adoc[Governance]

  • xref:utilities.adoc[Utilities]

  • xref:subgraphs::index.adoc[Subgraphs]

  • xref:faq.adoc[FAQ]


Modules ROOT Pages Access Control (`docs/modules/ROOT/pages/access-control.adoc`)

= Access Control

Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. It is therefore critical to understand how you implement it, lest someone else https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7[steals your whole system].

[[ownership-and-ownable]]
== Ownership and Ownable

The most common and basic form of access control is the concept of ownership: there's an account that is the owner of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user.

OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[Ownable] for implementing ownership in your contracts.

solidity
include::api:example$access-control/MyContractOwnable.sol[]

At deployment, the xref:api:access.adoc#Ownable-owner--[owner] of an Ownable contract is set to the provided initialOwner parameter.

Ownable also lets you:

  • xref:api:access.adoc#Ownable-transferOwnership-address-[transferOwnership] from the owner account to a new one, and
  • xref:api:access.adoc#Ownable-renounceOwnership--[renounceOwnership] for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over.

WARNING: Removing the owner altogether will mean that administrative tasks that are protected by onlyOwner will no longer be callable!

Ownable is a simple and effective way to implement access control, but you should be mindful of the dangers associated with transferring the ownership to an incorrect account that can't interact with this contract anymore. An alternative to this problem is using xref:api:access.adoc#Ownable2Step[Ownable2Step]; a variant of Ownable that requires the new owner to explicitly accept the ownership transfer by calling xref:api:access.adoc#Ownable2Step-acceptOwnership--[acceptOwnership].

Note that a contract can also be the owner of another one! This opens the door to using, for example, a https://safe.global[Gnosis Safe], an https://aragon.org[Aragon DAO], or a totally custom contract that you create.

In this way, you can use composability to add additional layers of access control complexity to your contracts. Instead of having a single regular Ethereum account (Externally Owned Account, or EOA) as the owner, you could use a 2-of-3 multisig run by your project leads, for example. Prominent projects in the space, such as https://makerdao.com[MakerDAO], use systems similar to this one.

[[role-based-access-control]]
== Role-Based Access Control

While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of authorization are often needed. You may want an account to have permission to ban users from a system, but not create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[_Role-Based Access Control (RBAC)_] offers flexibility in this regard.

In essence, we will be defining multiple roles, each allowed to perform different sets of actions. An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using onlyOwner. This check can be enforced through the onlyRole modifier. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.

Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.

[[using-access-control]]
=== Using AccessControl

OpenZeppelin Contracts provides xref:api:access.adoc#AccessControl[AccessControl] for implementing role-based access control. Its usage is straightforward: for each role that you want to define,
you will create a new role identifier that is used to grant, revoke, and check if an account has that role.

Here's a simple example of using AccessControl in an xref:erc20.adoc[ERC-20 token] to define a 'minter' role, which allows accounts that have it to create new tokens:

[source,solidity]

include::api:example$access-control/AccessControlERC20MintBase.sol[]

NOTE: Make sure you fully understand how xref:api:access.adoc#AccessControl[AccessControl] works before using it on your system, or copy-pasting the examples from this guide.

While clear and explicit, this isn't anything we wouldn't have been able to achieve with Ownable. Indeed, where AccessControl shines is in scenarios where granular permissions are required, which can be implemented by defining multiple roles.

Let's augment our ERC-20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the onlyRole modifier:

[source,solidity]

include::api:example$access-control/AccessControlERC20MintOnlyRole.sol[]

So clean! By splitting concerns this way, more granular levels of permission may be implemented than were possible with the simpler ownership approach to access control. Limiting what each component of a system is able to do is known as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good security practice. Note that each account may still have more than one role, if so desired.

[[granting-and-revoking]]
=== Granting and Revoking Roles

The ERC-20 token example above uses _grantRole, an internal function that is useful when programmatically assigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts?

By default, accounts with a role cannot grant it or revoke it from other accounts: all having a role does is making the hasRole check pass. To grant and revoke roles dynamically, you will need help from the role's admin.

Every role has an associated admin role, which grants permission to call the grantRole and revokeRole functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it.

This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. AccessControl includes a special role, called DEFAULT_ADMIN_ROLE, which acts as the default admin role for all roles. An account with this role will be able to manage any other role, unless _setRoleAdmin is used to select a new admin role.

Since it is the admin for all roles by default, and in fact it is also its own admin, this role carries significant risk. To mitigate this risk we provide xref:api:access.adoc#AccessControlDefaultAdminRules[AccessControlDefaultAdminRules], a recommended extension of AccessControl that adds a number of enforced security measures for this role: the admin is restricted to a single account, with a 2-step transfer procedure with a delay between steps.

Let's take a look at the ERC-20 token example, this time taking advantage of the default admin role:

[source,solidity]

include::api:example$access-control/AccessControlERC20MintMissing.sol[]

Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. However, because those roles' admin role is the default admin role, and that role was granted to msg.sender, that same account can call grantRole to give minting or burning permission, and revokeRole to remove it.

Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary over time. It can also be used to support use cases such as https://en.wikipedia.org/wiki/Know_your_customer[KYC], where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction.

[[querying-privileged-accounts]]
=== Querying Privileged Accounts

Because accounts might <<granting-and-revoking, grant and revoke roles>> dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows proving certain properties about a system, such as that an administrative account is a multisig or a DAO, or that a certain role has been removed from all users, effectively disabling any associated functionality.

The base AccessControl contract provides role-based access control, but it does not support on-chain enumeration of role members. To track which accounts hold a role, you should instead rely on the xref:api:access.adoc#AccessControl-RoleGranted-bytes32-address-address-[RoleGranted] and xref:api:access.adoc#AccessControl-RoleRevoked-bytes32-address-address-[RoleRevoked] events, which can be processed off-chain. If on-chain enumeration is required, use the xref:api:access.adoc#AccessControlEnumerable#AccessControlEnumerable[AccessControlEnumerable] extension.

This contract uses EnumerableSet internally and provides the following functions:

  • xref:api:access.adoc#AccessControlEnumerable-getRoleMemberCount-bytes32-[getRoleMemberCount]
  • xref:api:access.adoc#AccessControlEnumerable-getRoleMember-bytes32-uint256-[getRoleMember]
  • xref:api:access.adoc#AccessControlEnumerable-getRoleMembers-bytes32-[getRoleMembers]

These can be used to iterate over the accounts that have been granted a role:

javascript
const minterCount = await myToken.getRoleMemberCount(MINTER_ROLE);

const members = [];
for (let i = 0; i < minterCount; ++i) {
    members.push(await myToken.getRoleMember(MINTER_ROLE, i));
}

== Delayed operation

Access control is essential to prevent unauthorized access to critical functions. These functions may be used to mint tokens, freeze transfers or perform an upgrade that completely changes the smart contract logic. While xref:api:access.adoc#Ownable[Ownable] and xref:api:access.adoc#AccessControl[AccessControl] can prevent unauthorized access, they do not address the issue of a misbehaving administrator attacking their own system to the prejudice of their users.

This is the issue the xref:api:governance.adoc#TimelockController[TimelockController] is addressing.

The xref:api:governance.adoc#TimelockController[TimelockController] is a proxy that is governed by proposers and executors. When set as the owner/admin/controller of a smart contract, it ensures that whichever maintenance operation is ordered by the proposers is subject to a delay. This delay protects the users of the smart contract by giving them time to review the maintenance operation and exit the system if they consider it is in their best interest to do so.

=== Using TimelockController

By default, the address that deployed the xref:api:governance.adoc#TimelockController[TimelockController] gets administration privileges over the timelock. This role grants the right to assign proposers, executors, and other administrators.

The first step in configuring the xref:api:governance.adoc#TimelockController[TimelockController] is to assign at least one proposer and one executor. These can be assigned during construction or later by anyone with the administrator role. These roles are not exclusive, meaning an account can have both roles.

Roles are managed using the xref:api:access.adoc#AccessControl[AccessControl] interface and the bytes32 values for each role are accessible through the DEFAULT_ADMIN_ROLE, PROPOSER_ROLE, EXECUTOR_ROLE, and CANCELLER_ROLE constants.

There is an additional feature built on top of AccessControl: giving the executor role to address(0) opens access to anyone to execute a proposal once the timelock has expired. This feature, while useful, should be used with caution.

At this point, with both a proposer and an executor assigned, the timelock can perform operations.

An optional next step is for the deployer to renounce its administrative privileges and leave the timelock self-administered. If the deployer decides to do so, all further maintenance, including assigning new proposers/schedulers or changing the timelock duration will have to follow the timelock workflow. This links the governance of the timelock to the governance of contracts attached to the timelock, and enforces a delay on timelock maintenance operations.

WARNING: If the deployer renounces administrative rights in favour of timelock itself, assigning new proposers or executors will require a timelocked operation. This means that if the accounts in charge of any of these two roles become unavailable, then the entire contract (and any contract it controls) becomes locked indefinitely.

With both the proposer and executor roles assigned and the timelock in charge of its own administration, you can now transfer the ownership/control of any contract to the timelock.

TIP: A recommended configuration is to grant both roles to a secure governance contract such as a DAO or a multisig, and to additionally grant the executor role to a few EOAs held by people in charge of helping with the maintenance operations. These wallets cannot take over control of the timelock but they can help smoothen the workflow.

=== Minimum delay

Operations executed by the xref:api:governance.adoc#TimelockController[TimelockController] are not subject to a fixed delay but rather a minimum delay. Some major updates might call for a longer delay. For example, if a delay of just a few days might be sufficient for users to audit a minting operation, it makes sense to use a delay of a few weeks, or even a few months, when scheduling a smart contract upgrade.

The minimum delay (accessible through the xref:api:governance.adoc#TimelockController-getMinDelay--[getMinDelay] method) can be updated by calling the xref:api:governance.adoc#TimelockController-updateDelay-uint256-[updateDelay] function. Bear in mind that access to this function is only accessible by the timelock itself, meaning this maintenance operation has to go through the timelock itself.

[[access-management]]
== Access Management

For a system of contracts, better integrated role management can be achieved with an xref:api:access.adoc#AccessManager[AccessManager] instance. Instead of managing each contract's permission separately, AccessManager stores all the permissions in a single contract, making your protocol easier to audit and maintain.

Although xref:api:access.adoc#AccessControl[AccessControl] offers a more dynamic solution for adding permissions to your contracts than Ownable, decentralized protocols tend to become more complex after integrating new contract instances and requires you to keep track of permissions separately in each contract. This increases the complexity of permissions management and monitoring across the system.

image::access-control-multiple.svg[Access Control multiple]

Protocols managing permissions in production systems often require more integrated alternatives to fragmented permissions through multiple AccessControl instances.

image::access-manager.svg[AccessManager]

The AccessManager is designed around the concept of role and target functions:

  • Roles are granted to accounts (addresses) following a many-to-many approach for flexibility. This means that each user can have one or multiple roles and multiple users can have the same role.
  • Access to a restricted target function is limited to one role. A target function is defined by one https://docs.soliditylang.org/en/v0.8.20/abi-spec.html#function-selector[function selector] on one contract (called target).

For a call to be authorized, the caller must bear the role that is assigned to the current target function (contract address + function selector).

image::access-manager-functions.svg[AccessManager functions]

=== Using AccessManager

OpenZeppelin Contracts provides xref:api:access.adoc#AccessManager[AccessManager] for managing roles across any number of contracts. The AccessManager itself is a contract that can be deployed and used out of the box. It sets an initial admin in the constructor who will be allowed to perform management operations.

In order to restrict access to some functions of your contract, you should inherit from the xref:api:access.adoc#AccessManaged[AccessManaged] contract provided along with the manager. This provides the restricted modifier that can be used to protect any externally facing function. Note that you will have to specify the address of the AccessManager instance (xref:api:access.adoc#AccessManaged-constructor-address-[initialAuthority]) in the constructor so the restricted modifier knows which manager to use for checking permissions.

Here's a simple example of an xref:tokens.adoc#ERC20[ERC-20 token] that defines a mint function that is restricted by an xref:api:access.adoc#AccessManager[AccessManager]:

solidity
include::api:example$access-control/AccessManagedERC20MintBase.sol[]

NOTE: Make sure you fully understand how xref:api:access.adoc#AccessManager[AccessManager] works before using it or copy-pasting the examples from this guide.

Once the managed contract has been deployed, it is now under the manager's control. The initial admin can then assign the minter role to an address and also allow the role to call the mint function. For example, this is demonstrated in the following Javascript code using Ethers.js:

javascript
// const target = ...;
// const user = ...;
const MINTER = 42n; // Roles are uint64 (0 is reserved for the ADMIN_ROLE)

// Grant the minter role with no execution delay
await manager.grantRole(MINTER, user, 0);

// Allow the minter role to call the function selector
// corresponding to the mint function
await manager.setTargetFunctionRole(
    target,
    ['0x40c10f19'], // bytes4(keccak256('mint(address,uint256)'))
    MINTER
);

Even though each role has its own list of function permissions, each role member (address) has an execution delay that will dictate how long the account should wait to execute a function that requires its role. Delayed operations must have the xref:api:access.adoc#AccessManager-schedule-address-bytes-uint48-[schedule] function called on them first in the AccessManager before they can be executed, either by calling the target function or using the AccessManager's xref:api:access.adoc#AccessManager-execute-address-bytes-[execute] function.

Additionally, roles can have a granting delay that prevents adding members immediately. The AccessManager admins can set this grant delay as follows:

javascript
const HOUR = 60 * 60;

const GRANT_DELAY = 24 * HOUR;
const EXECUTION_DELAY = 5 * HOUR;
const ACCOUNT = "0x...";

await manager.connect(initialAdmin).setGrantDelay(MINTER, GRANT_DELAY);

// The role will go into effect after the GRANT_DELAY passes
await manager.connect(initialAdmin).grantRole(MINTER, ACCOUNT, EXECUTION_DELAY);

Note that roles do not define a name. As opposed to the xref:api:access.adoc#AccessControl[AccessControl] case, roles are identified as numeric values instead of being hardcoded in the contract as bytes32 values. It is still possible to allow for tooling discovery (e.g. for role exploration) using role labeling with the xref:api:access.adoc#AccessManager-labelRole-uint64-string-[labelRole] function.

javascript
await manager.labelRole(MINTER, "MINTER");

Given the admins of the AccessManaged can modify all of its permissions, it's recommended to keep only a single admin address secured under a multisig or governance layer. To achieve this, it is possible for the initial admin to set up all the required permissions, targets, and functions, assign a new admin, and finally renounce its admin role.

For improved incident response coordination, the manager includes a mode where administrators can completely close a target contract. When closed, all calls to restricted target functions in a target contract will revert.

Closing and opening contracts don't alter any of their settings, neither permissions nor delays. Particularly, the roles required for calling specific target functions are not modified.

This mode is useful for incident response operations that require temporarily shutting down a contract in order to evaluate emergencies and reconfigure permissions.

javascript
const target = await myToken.getAddress();

// Token's `restricted` functions closed
await manager.setTargetClosed(target, true);

// Token's `restricted` functions open
await manager.setTargetClosed(target, false);

WARNING: Even if an AccessManager defines permissions for a target function, these won't be applied if the managed contract instance is not using the xref:api:access.adoc#AccessManaged-restricted--[restricted] modifier for that function, or if its manager is a different one.

=== Role Admins and Guardians

An important aspect of the AccessControl contract is that roles aren't granted nor revoked by role members. Instead, it relies on the concept of a role admin for granting and revoking.

In the case of the AccessManager, the same rule applies and only the role's admins are able to call xref:api:access.adoc#AccessManager-grantRole-uint64-address-uint32-[grant] and xref:api:access.adoc#AccessManager-revokeRole-uint64-address-[revoke] functions. Note that calling these functions will be subject to the execution delay that the executing role admin has.

Additionally, the AccessManager stores a guardian as an extra protection for each role. This guardian has the ability to cancel operations that have been scheduled by any role member with an execution delay. Consider that a role will have its initial admin and guardian default to the ADMIN_ROLE (0).

IMPORTANT: Be careful with the members of ADMIN_ROLE, since it acts as the default admin and guardian for every role. A misbehaved guardian can cancel operations at will, affecting the AccessManager's operation.

=== Manager configuration

The AccessManager provides a built-in interface for configuring permission settings that can be accessed by its ADMIN_ROLE members.

This configuration interface includes the following functions:

  • Add a label to a role using the xref:api:access.adoc#AccessManager-labelRole-uint64-string-[labelRole] function.
  • Assign the admin and guardian of a role with xref:api:access.adoc#AccessManager-setRoleAdmin-uint64-uint64-[setRoleAdmin] and xref:api:access.adoc#AccessManager-setRoleGuardian-uint64-uint64-[setRoleGuardian].
  • Set each role's grant delay via xref:api:access.adoc#AccessManager-setGrantDelay-uint64-uint32-[setGrantDelay].

As an admin, some actions will require a delay. Similar to each member's execution delay, some admin operations require waiting for execution and should follow the xref:api:access.adoc#AccessManager-schedule-address-bytes-uint48-[schedule] and xref:api:access.adoc#AccessManager-execute-address-bytes-[execute] workflow.

More specifically, these delayed functions are those for configuring the settings of a specific target contract. The delay applied to these functions can be adjusted by the manager admins with xref:api:access.adoc#AccessManager-setTargetAdminDelay-address-uint32-[setTargetAdminDelay].

The delayed admin actions are:

  • Updating an AccessManaged contract xref:api:access.adoc#AccessManaged-authority--[authority] using xref:api:access.adoc#AccessManager-updateAuthority-address-address-[updateAuthority].
  • Closing or opening a target via xref:api:access.adoc#AccessManager-setTargetClosed-address-bool-[setTargetClosed].
  • Changing permissions of whether a role can call a target function with xref:api:access.adoc#AccessManager-setTargetFunctionRole-address-bytes4---uint64-[setTargetFunctionRole].

=== Manager Enumerability

Similar to AccessControl, accounts might be granted and revoked roles dynamically in an AccessManager, making it challenging to determine which accounts hold a particular role at any given time. This capability is essential for proving certain properties about a system, such as verifying that an administrative role is held by a multisig or DAO, or that a certain role has been completely removed to disable associated functionality.

The base AccessManager contract provides comprehensive role-based access control but does not support on-chain enumeration of role members or target function permissions by default. To track which accounts hold roles and which functions are assigned to roles, you should rely on the xref:api:access.adoc#AccessManager-RoleGranted-uint64-address-uint32-uint48-bool-[RoleGranted], xref:api:access.adoc#AccessManager-RoleRevoked-uint64-address-[RoleRevoked], and xref:api:access.adoc#AccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-[TargetFunctionRoleUpdated] events, which can be processed off-chain.

If on-chain enumeration is required, it can be added implemented on top of the existing logic:

solidity
include::api:example$AccessManagerEnumerable.sol[]

NOTE: The enumerable example only enumerates members of a role and functions that each role can call. Yet, it's possible to enumerate roles active (i.e. roles granted to at least 1 member), guardians and admins.

This adds function that can be queried to iterate over the accounts that have been granted a role and the functions that a role is allowed to call on specific targets:

javascript
// Enumerate role members
const minterCount = await accessManager.getRoleMemberCount(MINTER_ROLE);

const members = [];
for (let i = 0; i < minterCount; ++i) {
    members.push(await accessManager.getRoleMember(MINTER_ROLE, i));
}

// Or get all members at once
const allMembers = await accessManager.getRoleMembers(MINTER_ROLE, 0, ethers.MaxUint256);

// Enumerate target functions for a role
const target = await myToken.getAddress();
const functionCount = await accessManager.getRoleTargetFunctionCount(MINTER_ROLE, target);

const functions = [];
for (let i = 0; i < functionCount; ++i) {
    functions.push(await accessManager.getRoleTargetFunction(MINTER_ROLE, target, i));
}

// Or get all functions at once
const allFunctions = await accessManager.getRoleTargetFunctions(MINTER_ROLE, target, 0, ethers.MaxUint256);

=== Using with Ownable

Contracts already inheriting from xref:api:access.adoc#Ownable[Ownable] can migrate to AccessManager by transferring ownership to the manager. After that, all calls to functions with the onlyOwner modifier should be called through the manager's xref:api:access.adoc#AccessManager-execute-address-bytes-[execute] function, even if the caller doesn't require a delay.

javascript
await ownable.connect(owner).transferOwnership(accessManager);

=== Using with AccessControl

For systems already using xref:api:access.adoc#AccessControl[AccessControl], the DEFAULT_ADMIN_ROLE can be granted to the AccessManager after revoking every other role. Subsequent calls should be made through the manager's xref:api:access.adoc#AccessManager-execute-address-bytes-[execute] method, similar to the Ownable case.

javascript
// Revoke old roles
await accessControl.connect(admin).revokeRole(MINTER_ROLE, account);

// Grant the admin role to the access manager
await accessControl.connect(admin).grantRole(DEFAULT_ADMIN_ROLE, accessManager);

await accessControl.connect(admin).renounceRole(DEFAULT_ADMIN_ROLE, admin);

NOTE: After migrating to AccessManager, the msg.sender in restricted functions will be the AccessManager contract itself through the xref:api:access.adoc#AccessManager-execute-address-bytes-[execute] function, not the original caller. This is a fundamental change in how access control works and may require updates to your contract logic or frontend integration.


Modules ROOT Pages Account Abstraction (`docs/modules/ROOT/pages/account-abstraction.adoc`)

= Account Abstraction

Unlike Externally Owned Accounts (EOAs), smart contracts may contain arbitrary verification logic based on authentication mechanisms different from 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 verificationGasLimit (16 bytes) and callGasLimit (16 bytes)
    uint256 preVerificationGas;
    bytes32 gasFees; // concatenation of maxPriorityFeePerGas (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 factors like calldata size, signature complexity, and bundler-specific serialization costs.

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 contract 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 from 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 xref:paymasters.adoc[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 their own storage during the validation phase, they might easily violate ERC-7562 storage access rules in indirect 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 https://eips.ethereum.org/EIPS/eip-1271[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.


Modules ROOT Pages Accounts (`docs/modules/ROOT/pages/accounts.adoc`)

= 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/cryptography.adoc#AbstractSigner[AbstractSigner], which requires implementing the internal xref:api:utils/cryptography.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 set up 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.

++++


++++

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#SignerEIP7702[SignerEIP7702]: 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].
  • xref:community-contracts::api:utils/cryptography.adoc#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 a user operation, your account will be created deterministically (i.e. its code and address can be predicted) using 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/contracts/utils/cryptography/signers/SignerECDSA.sol";

contract MyAccount is Initializable, Account, SignerECDSA, ... {
// ...

text
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 prevents signatures for this account from being 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 {SignerEIP7702} from "@openzeppelin/contracts/utils/cryptography/signers/SignerEIP7702.sol";

contract MyAccount is Account, SignerEIP7702, 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 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',
client: walletClient,
});

const userOp = {
sender: '0x',
nonce: await entrypoint.read.getNonce([sender, 0n]),
initCode: "0x" as Hex,
callData: '0x',
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',
encodeFunctionData({
abi: [/* ACCOUNT ABI */],
functionName: "",
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',
functionName: "handleOps",
args: [[userOp], eoa.address],
})
.then((txHash) =>
publicClient.waitForTransactionReceipt({
hash: txHash,
})
);

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:utils/cryptography.adoc#ERC7739[ERC7739], a utility that implements xref:api:interfaces.adoc#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 EIP-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 xref:community-contracts::account-modules.adoc[account modules] section.


Modules ROOT Pages Backwards Compatibility (`docs/modules/ROOT/pages/backwards-compatibility.adoc`)

= Backwards Compatibility
:page-aliases: releases-stability.adoc

OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. Patch and minor updates will generally be backwards compatible, with rare exceptions as detailed below. Major updates should be assumed incompatible with previous releases. On this page, we provide details about these guarantees.

== API

In backwards compatible releases, all changes should be either additions or modifications to internal implementation details. Most code should continue to compile and behave as expected. The exceptions to this rule are listed below.

=== Security

Infrequently a patch or minor update will remove or change an API in a breaking way, but only if the previous API is considered insecure. These breaking changes will be noted in the changelog and release notes, and published along with a security advisory.

=== Draft or Pre-Final ERCs

ERCs that are not Final can change in incompatible ways. For this reason, we avoid shipping implementations of ERCs before they are Final. Some exceptions are made for ERCs that have been published for a long time and seem unlikely to change. Implementations for ERCs that may have breaking changes are published in files named draft-*.sol to make that condition explicit. There is no backwards compatibility guarantee for content in files prefixed with draft.

Standards that have achieved widespread adoption with strong backwards compatibility expectations from the community may be treated as de-facto finalized and published without the draft- prefix, as extensive ecosystem reliance makes breaking changes highly unlikely.

=== Virtual & Overrides

Almost all functions in this library are virtual with some exceptions, but this does not mean that overrides are encouraged. There is a subset of functions that are designed to be overridden. By defining overrides outside of this subset you are potentially relying on internal implementation details. We make efforts to preserve backwards compatibility even in these cases but it is extremely difficult and easy to accidentally break. Caution is advised.

Additionally, some minor updates may result in new compilation errors of the kind "two or more base classes define function with same name and parameter types" or "need to specify overridden contract", due to what Solidity considers ambiguity in inherited functions. This should be resolved by adding an override that invokes the function via super.

See xref:extending-contracts.adoc[Extending Contracts] for more about virtual and overrides.

=== Structs

Struct members with an underscore prefix should be considered "private" and may break in minor versions. Struct data should only be accessed and modified through library functions.

=== Errors

The specific error format and data that is included with reverts should not be assumed stable unless otherwise specified.

=== Major Releases

Major releases should be assumed incompatible. Nevertheless, the external interfaces of contracts will remain compatible if they are standardized, or if the maintainers judge that changing them would cause significant strain on the ecosystem.

An important aspect that major releases may break is "upgrade compatibility", in particular storage layout compatibility. It will never be safe for a live contract to upgrade from one major release to another.

== Storage Layout

Minor and patch updates always preserve storage layout compatibility. This means that a live contract can be upgraded from one minor to another without corrupting the storage layout. In some cases it may be necessary to initialize new state variables when upgrading, although we expect this to be infrequent.

We recommend using xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins or CLI] to ensure storage layout safety of upgrades.

== Solidity Version

The minimum Solidity version required to compile the contracts will remain unchanged in minor and patch updates. New contracts introduced in minor releases may make use of newer Solidity features and require a more recent version of the compiler.


Modules ROOT Pages Eoa Delegation (`docs/modules/ROOT/pages/eoa-delegation.adoc`)

= 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 a 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[EIP-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 xref:paymasters.adoc[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#[SignerEIP7702] 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/MyAccountEIP7702.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('');
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', // 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',
to: eoa.address,
})
.then((txHash) =>
publicClient.waitForTransactionReceipt({
hash: txHash,
})
);

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, use namespaced storage that avoids 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 xref:upgrades-plugins::writing-upgradeable.adoc[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#SignerEIP7702[SignerEIP7702] 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',
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.


Modules ROOT Pages Erc1155 (`docs/modules/ROOT/pages/erc1155.adoc`)

= ERC-1155

ERC-1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[fungibility-agnostic] and gas-efficient xref:tokens.adoc#but-first-coffee-a-primer-on-token-contracts[token contract].

TIP: ERC-1155 draws ideas from all of xref:erc20.adoc[ERC-20], xref:erc721.adoc[ERC-721], and https://eips.ethereum.org/EIPS/eip-777[ERC-777]. If you're unfamiliar with those standards, head to their guides before moving on.

[[multi-token-standard]]
== Multi Token Standard

The distinctive feature of ERC-1155 is that it uses a single smart contract to represent multiple tokens at once. This is why its xref:api:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256-[balanceOf] function differs from ERC-20's and ERC-777's: it has an additional id argument for the identifier of the token that you want to query the balance of.

This is similar to how ERC-721 does things, but in that standard a token id has no concept of balance: each token is non-fungible and exists or doesn't. The ERC-721 xref:api:token/ERC721.adoc#IERC721-balanceOf-address-[balanceOf] function refers to how many different tokens an account has, not how many of each. On the other hand, in ERC-1155 accounts have a distinct balance for each token id, and non-fungible tokens are implemented by simply minting a single one of them.

This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new contract for each token type, a single ERC-1155 token contract can hold the entire system state, reducing deployment costs and complexity.

[[batch-operations]]
== Batch Operations

Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, xref:api:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256---[balanceOfBatch] and xref:api:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[safeBatchTransferFrom], that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive.

In the spirit of the standard, we've also included batch operations in the non-standard functions, such as xref:api:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes-[_mintBatch].

== Constructing an ERC-1155 Token Contract

We'll use ERC-1155 to track multiple items in our game, which will each have their own unique attributes. We mint all items to the deployer of the contract, which we can later transfer to players. Players are free to keep their tokens or trade them with other people as they see fit, as they would any other asset on the blockchain!

For simplicity, we will mint all items in the constructor, but you could add minting functionality to the contract to mint on demand to players.

TIP: For an overview of minting mechanisms, check out xref:erc20-supply.adoc[Creating ERC-20 Supply].

Here's what a contract for tokenized items might look like:

[source,solidity]

include::api:example$token/ERC1155/GameItems.sol[]

Note that for our Game Items, Gold is a fungible token whilst Thor's Hammer is a non-fungible token as we minted only one.

The xref:api:token/ERC1155.adoc#ERC1155[ERC1155] contract includes the optional extension xref:api:token/ERC1155.adoc#IERC1155MetadataURI[IERC1155MetadataURI]. That's where the xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[uri] function comes from: we use it to retrieve the metadata uri.

Also note that, unlike ERC-20, ERC-1155 lacks a decimals field, since each token is distinct and cannot be partitioned.

Once deployed, we will be able to query the deployer’s balance: [source,javascript]

gameItems.balanceOf(deployerAddress,3)
1000000000


We can transfer items to player accounts: [source,javascript]

gameItems.safeTransferFrom(deployerAddress, playerAddress, 2, 1, "0x0")
gameItems.balanceOf(playerAddress, 2)
1
gameItems.balanceOf(deployerAddress, 2)
0


We can also batch transfer items to player accounts and get the balance of batches: [source,javascript]

gameItems.safeBatchTransferFrom(deployerAddress, playerAddress, [0,1,3,4], [50,100,1,1], "0x0")
gameItems.balanceOfBatch([playerAddress,playerAddress,playerAddress,playerAddress,playerAddress], [0,1,2,3,4])
[50,100,1,1,1]


The metadata uri can be obtained:

[source,javascript]

gameItems.uri(2)
"https://game.example/api/item/{id}.json"


The uri can include the string ++{id}++ which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters.

For token ID 2 and uri ++https://game.example/api/item/{id}.json++ clients would replace ++{id}++ with 0000000000000000000000000000000000000000000000000000000000000002 to retrieve JSON at https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json.

The JSON document for token ID 2 might look something like:

[source,json]

{
"name": "Thor's hammer",
"description": "Mjölnir, the legendary hammer of the Norse god of thunder.",
"image": "https://game.example/item-id-8u5h2m.png",
"strength": 20

}

For more information about the metadata JSON Schema, check out the https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema[ERC-1155 Metadata URI JSON Schema].

NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!

TIP: If you'd like to put all item information on-chain, you can override the uri() function in your ERC-1155 contract (or use xref:api:token/ERC1155.adoc#ERC1155URIStorage[ERC1155URIStorage]) to return a xref:utilities.adoc#base64[Base64] Data URI with the JSON schema encoded, though it will be rather costly. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide

[[sending-to-contracts]]
== Sending Tokens to Contracts

A key difference when using xref:api:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[safeTransferFrom] is that token transfers to other contracts may revert with the following custom error:

[source,text]

ERC1155InvalidReceiver("
")

This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to prevent tokens from being locked forever. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k GNT tokens], and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error.

In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[ERC1155Holder] which handles the registering for us. However, we need to remember to implement functionality to allow tokens to be transferred out of our contract:

[source,solidity]

include::api:example$token/ERC1155/MyERC1155HolderContract.sol[]

We can also implement more complex scenarios using the xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[onERC1155Received] and xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[onERC1155BatchReceived] functions.


Modules ROOT Pages Erc20 Supply (`docs/modules/ROOT/pages/erc20-supply.adoc`)

= Creating ERC-20 Supply

In this guide, you will learn how to create an ERC-20 token with a custom supply mechanism. We will showcase two idiomatic ways to use OpenZeppelin Contracts for this purpose that you will be able to apply to your smart contract development practice.

The standard interface implemented by tokens built on Ethereum is called ERC-20, and Contracts includes a widely used implementation of it: the aptly named xref:api:token/ERC20.adoc[ERC20] contract. This contract, like the standard itself, is quite simple and bare-bones. In fact, if you try to deploy an instance of ERC20 as-is it will be quite literally useless... it will have no supply! What use is a token with no supply?

The way that supply is created is not defined in the ERC-20 document. Every token is free to experiment with its own mechanisms, ranging from the most decentralized to the most centralized, from the most naive to the most researched, and more.

[[fixed-supply]]
== Fixed Supply

Let's say we want a token with a fixed supply of 1000, initially allocated to the account that deploys the contract. If you've used Contracts v1, you may have written code like the following:

[source,solidity]

contract ERC20FixedSupply is ERC20 {
constructor() {
totalSupply += 1000;
balances[msg.sender] += 1000;
}

}

Starting with Contracts v2, this pattern is not only discouraged, but disallowed. The variables totalSupply and balances are now private implementation details of ERC20, and you can't directly write to them. Instead, there is an internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[_mint] function that will do exactly this:

[source,solidity]

contract ERC20FixedSupply is ERC20 {
constructor() ERC20("Fixed", "FIX") {
_mint(msg.sender, 1000);
}

}

Encapsulating state like this makes it safer to extend contracts. For instance, in the first example we had to manually keep the totalSupply in sync with the modified balances, which is easy to forget. In fact, we omitted something else that is also easily forgotten: the Transfer event that is required by the standard, and which is relied on by some clients. The second example does not have this bug, because the internal _mint function takes care of it.

[[rewarding-block-proposers]]
== Rewarding Block Proposers

The internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[_mint] function is the key building block that allows us to write ERC-20 extensions that implement a supply mechanism.

The mechanism we will implement is a token reward for the block proposer (the address that receives transaction fees). In Solidity, we can access this address using the global variable block.coinbase. Note that in Proof of Stake networks, block.coinbase refers to the fee recipient address, not a miner. We will mint a token reward to this address whenever someone calls the function mintMinerReward() on our token. The mechanism may sound silly, but you never know what kind of dynamic this might result in, and it's worth analyzing and experimenting with!

[source,solidity]

contract ERC20WithMinerReward is ERC20 {
constructor() ERC20("Reward", "RWD") {}

text
function mintMinerReward() public {
    _mint(block.coinbase, 1000);
}

}

As we can see, _mint makes it super easy to do this correctly.

[[automating-the-reward]]
== Automating the Reward

So far our supply mechanism was triggered manually, but ERC20 also allows us to extend the core functionality of the token through the xref:api:token/ERC20.adoc#ERC20-_update-address-address-uint256-[_update] function.

Adding to the supply mechanism from the previous section, we can use this function to mint a block proposer reward for every token transfer that is included in the blockchain.

solidity
include::api:example$ERC20WithAutoMinerReward.sol[]

[[wrapping-up]]
== Wrapping Up

We've seen how to implement an ERC-20 supply mechanism: internally through _mint. Hopefully this has helped you understand how to use OpenZeppelin Contracts and some of the design principles behind it, and you can apply them to your own smart contracts.


Modules ROOT Pages Erc20 (`docs/modules/ROOT/pages/erc20.adoc`)

= ERC-20

An ERC-20 token contract keeps track of xref:tokens.adoc#different-kinds-of-tokens[fungible tokens]: any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC-20 tokens useful for things like a medium of exchange currency, voting rights, staking, and more.

OpenZeppelin Contracts provides many ERC20-related contracts. On the xref:api:token/ERC20.adoc[API reference] you'll find detailed information on their properties and usage.

[[constructing-an-erc20-token-contract]]
== Constructing an ERC-20 Token Contract

Using Contracts, we can easily create our own ERC-20 token contract, which will be used to track Gold (GLD), an internal currency in a hypothetical game.

Here's what our GLD token might look like.

[source,solidity]

include::api:example$token/ERC20/GLDToken.sol[]

Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[ERC20] for both the basic standard implementation and the xref:api:token/ERC20.adoc#ERC20-name--[name], xref:api:token/ERC20.adoc#ERC20-symbol--[symbol], and xref:api:token/ERC20.adoc#ERC20-decimals--[decimals] optional extensions. Additionally, we're creating an initialSupply of tokens, which will be assigned to the address that deploys the contract.

TIP: For a more complete discussion of ERC-20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC-20 Supply].

That's it! Once deployed, we will be able to query the deployer's balance:

[source,javascript]

GLDToken.balanceOf(deployerAddress)
1000000000000000000000


We can also xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer] these tokens to other accounts:

[source,javascript]

GLDToken.transfer(otherAddress, 300000000000000000000)
GLDToken.balanceOf(otherAddress)
300000000000000000000
GLDToken.balanceOf(deployerAddress)
700000000000000000000


[[a-note-on-decimals]]
== A Note on decimals

Often, you'll want to be able to divide your tokens into arbitrary amounts: say, if you own 5 GLD, you may want to send 1.5 GLD to a friend, and keep 3.5 GLD to yourself. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send 1 or 2 tokens, but not 1.5.

To work around this, xref:api:token/ERC20.adoc#ERC20[ERC20] provides a xref:api:token/ERC20.adoc#ERC20-decimals--[decimals] field, which is used to specify how many decimal places a token has. To be able to transfer 1.5 GLD, decimals must be at least 1, since that number has a single decimal place.

How can this be achieved? It's actually very simple: a token contract can use larger integer values, so that a balance of 50 will represent 5 GLD, a transfer of 15 will correspond to 1.5 GLD being sent, and so on.

It is important to understand that decimals is only used for display purposes. All arithmetic inside the contract is still performed on integers, and it is the different user interfaces (wallets, exchanges, etc.) that must adjust the displayed values according to decimals. The total token supply and balance of each account are not specified in GLD: you need to divide by 10 ** decimals to get the actual GLD amount.

You'll probably want to use a decimals value of 18, just like Ether and most ERC-20 token contracts in use, unless you have a very special reason not to. When minting tokens or transferring them around, you will be actually sending the number num GLD * (10 ** decimals).

NOTE: By default, ERC20 uses a value of 18 for decimals. To use a different value, you will need to override the decimals() function in your contract.

solidity
function decimals() public view virtual override returns (uint8) {
  return 16;
}

So if you want to send 5 tokens using a token contract with 18 decimals, the method to call will actually be:

solidity
transfer(recipient, 5 * (10 ** 18));

Modules ROOT Pages Erc4626 (`docs/modules/ROOT/pages/erc4626.adoc`)

= ERC-4626
:stem: latexmath

https://eips.ethereum.org/EIPS/eip-4626[ERC-4626] is an extension of xref:erc20.adoc[ERC-20] that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault.

We provide a base implementation of ERC-4626 that includes a simple vault. This contract is designed in a way that allows developers to easily re-configure the vault's behavior, with minimal overrides, while staying compliant. In this guide, we will discuss some security considerations that affect ERC-4626. We will also discuss common customizations of the vault.

[[inflation-attack]]
== Security concern: Inflation attack

=== Visualizing the vault

In exchange for the assets deposited into an ERC-4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault.

  • If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets.
  • If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets.

In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line.

image::erc4626-rate-linear.png[Exchange rates in linear scale]

When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets.

image::erc4626-rate-loglog.png[Exchange rates in logarithmic scale]

In such a representation, widely different rates can be clearly visible in the same graph. This wouldn't be the case in linear scale.

image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale]

=== The attack

When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation.

For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares.

image::erc4626-deposit.png[Depositing assets]

In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%.

image::erc4626-mint.png[Minting shares]

Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens.

We can clearly see that the blue and green curves correspond to vaults that are safer than the yellow and orange curves.

The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe.

image::erc4626-attack.png[Inflation attack without protection]

Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only shareholder (from their donation), the attacker would steal all the tokens deposited.

An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked.

In math that gives:

  • stem:[a_0] the attacker deposit
  • stem:[a_1] the attacker donation
  • stem:[u] the user deposit

[%header,cols=4*]
|===
| | Assets | Shares | Rate

| initial
| stem:[0]

stem:[0]

| after attacker's deposit
| stem:[a_0]
| stem:[a_0]
| stem:[1]

| after attacker's donation
| stem:[a_0+a_1]
| stem:[a_0]
| stem:[\frac{a_0}{a_0+a_1}]
|===

This means a deposit of stem:[u] will give stem:[\frac{u \times a_0}{a_0 + a_1}] shares.

For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that

[stem]
++++
\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0}
++++

Using stem:[a_0 = 1] and stem:[a_1 = u] is enough. So the attacker only needs stem:[u+1] assets to perform a successful attack.

It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user's deposit. In order to target stem:[\frac{u}{n}], the user needs to suffer rounding of a similar fraction, which means the user must receive at most stem:[n] shares. This results in:

[stem]
++++
\frac{u \times a_0}{a_0+a_1} < n \iff \frac{u}{n} < 1 + \frac{a_1}{a_0}
++++

In this scenario, the attack is stem:[n] times less powerful (in how much it is stealing) and costs stem:[n] times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings.

=== Defending with a virtual offset

The defense we propose is based on the approach used in link:https://github.com/boringcrypto/YieldBox[YieldBox]. It consists of two parts:

  • Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets.
  • Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty.

These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable for a developer to perform an attack.

Following the previous math definitions, we have:

  • stem:[\delta] the vault offset
  • stem:[a_0] the attacker deposit
  • stem:[a_1] the attacker donation
  • stem:[u] the user deposit

[%header,cols=4*]
|===
| | Assets | Shares | Rate

| initial
| stem:[1]
| stem:[10^\delta]
| stem:[10^\delta]

| after attacker's deposit
| stem:[1+a_0]
| stem:[10^\delta \times (1+a_0)]
| stem:[10^\delta]

| after attacker's donation
| stem:[1+a_0+a_1]
| stem:[10^\delta \times (1+a_0)]
| stem:[10^\delta \times \frac{1+a_0}{1+a_0+a_1}]
|===

One important thing to note is that the attacker only owns a fraction stem:[\frac{a_0}{1 + a_0}] of the shares, so when doing the donation, he will only be able to recover that fraction stem:[\frac{a_1 \times a_0}{1 + a_0}] of the donation. The remaining stem:[\frac{a_1}{1+a_0}] are captured by the vault.

[stem]
++++
\mathit{loss} = \frac{a_1}{1+a_0}
++++

When the user deposits stem:[u], he receives

[stem]
++++
10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1}
++++

For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that

[stem]
++++
10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1
++++

[stem]
++++
\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0}
++++

[stem]
++++
\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0}
++++

[stem]
++++
\iff 10^\delta \times u \le \mathit{loss}
++++

  • If the offset is 0, the attacker's loss is at least equal to the user's deposit.
  • If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user.

This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful.

The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively.

image::erc4626-attack-3a.png[Inflation attack without offset=3]
stem:[\delta = 3], stem:[a_0 = 1], stem:[a_1 = 10^5]

image::erc4626-attack-3b.png[Inflation attack without offset=3 and an attacker deposit that limits its losses]
stem:[\delta = 3], stem:[a_0 = 100], stem:[a_1 = 10^5]

image::erc4626-attack-6.png[Inflation attack without offset=6]
stem:[\delta = 6], stem:[a_0 = 1], stem:[a_1 = 10^5]

[[fees]]
== Custom behavior: Adding fees to the vault

In an ERC-4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. In both cases it is essential to remain compliant with the ERC-4626 requirements with regard to the preview functions.

For example, if calling deposit(100, receiver), the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by previewDeposit(100). Similarly, previewMint should account for the fees that the user will have to pay on top of share's cost.

As for the Deposit event, while this is less clear in the EIP spec itself, there seems to be consensus that it should include the number of assets paid for by the user, including the fees.

On the other hand, when withdrawing assets, the number given by the user should correspond to what he receives. Any fees should be added to the quote (in shares) performed by previewWithdraw.

The Withdraw event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted).

The consequence of this design is that both the Deposit and Withdraw events will describe two exchange rates. The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault.

The following example describes how fees proportional to the deposited/withdrawn amount can be implemented:

solidity
include::api:example$ERC4626Fees.sol[]

Modules ROOT Pages Erc6909 (`docs/modules/ROOT/pages/erc6909.adoc`)

= ERC-6909

ERC-6909 is a draft EIP that draws on ERC-1155 learnings since it was published in 2018. The main goals of ERC-6909 are to decrease gas costs and complexity--this is mainly accomplished by removing batching and callbacks.

TIP: To understand the inspiration for a multi token standard, see the xref:erc1155.adoc#multi-token-standard[multi token standard] section within the EIP-1155 docs.

== Changes from ERC-1155

There are three main changes from ERC-1155 which are as follows:

. The removal of batch operations.
. The removal of transfer callbacks.
. Granularization in approvals--approvals can be set globally (as operators) or as amounts per token (inspired by ERC20).

== Constructing an ERC-6909 Token Contract

We'll use ERC-6909 to track multiple items in a game, each having their own unique attributes. All item types will be minted to the deployer of the contract, which we can later transfer to players. We'll also use the xref:api:token/ERC6909.adoc#ERC6909Metadata[ERC6909Metadata] extension to add decimals to our fungible items (the vanilla ERC-6909 implementation does not have decimals).

For simplicity, we will mint all items in the constructor--however, minting functionality could be added to the contract to mint on demand to players.

TIP: For an overview of minting mechanisms, check out xref:erc20-supply.adoc[Creating ERC-20 Supply].

Here's what a contract for tokenized items might look like:

[source,solidity]

include::api:example$token/ERC6909/ERC6909GameItems.sol[]

Note that there is no content URI functionality in the base implementation, but the xref:api:token/ERC6909.adoc#ERC6909ContentURI[ERC6909ContentURI] extension adds it. Additionally, the base implementation does not track total supplies, but the xref:api:token/ERC6909.adoc#ERC6909TokenSupply[ERC6909TokenSupply] extension tracks the total supply of each token id.

Once the contract is deployed, we will be able to query the deployer’s balance: [source,javascript]

gameItems.balanceOf(deployerAddress, 3)
1000000000


We can transfer items to player accounts: [source,javascript]

gameItems.transfer(playerAddress, 2, 1)
gameItems.balanceOf(playerAddress, 2)
1
gameItems.balanceOf(deployerAddress, 2)
0



Modules ROOT Pages Erc721 (`docs/modules/ROOT/pages/erc721.adoc`)

= ERC-721

We've discussed how you can make a fungible token using xref:erc20.adoc[ERC-20], but what if not all tokens are alike? This comes up in situations like real estate, voting rights, or collectibles, where some items are valued more than others, due to their usefulness, rarity, etc. ERC-721 is a standard for representing ownership of xref:tokens.adoc#different-kinds-of-tokens[non-fungible tokens], that is, where each token is unique.

ERC-721 is a more complex standard than ERC-20, with multiple optional extensions, and is split across a number of contracts. The OpenZeppelin Contracts provide flexibility regarding how these are combined, along with custom useful extensions. Check out the xref:api:token/ERC721.adoc[API Reference] to learn more about these.

== Constructing an ERC-721 Token Contract

We'll use ERC-721 to track items in our game, which will each have their own unique attributes. Whenever one is to be awarded to a player, it will be minted and sent to them. Players are free to keep their token or trade it with other people as they see fit, as they would any other asset on the blockchain! Please note that any account can call awardItem to mint items. To restrict what accounts can mint items we can add xref:access-control.adoc[Access Control].

Here's what a contract for tokenized items might look like:

[source,solidity]

include::api:example$token/ERC721/GameItem.sol[]

The xref:api:token/ERC721.adoc#ERC721URIStorage[ERC721URIStorage] contract is an implementation of ERC-721 that includes the metadata standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[IERC721Metadata]) as well as a mechanism for per-token metadata. That's where the xref:api:token/ERC721.adoc#ERC721URIStorage-_setTokenURI-uint256-string-[_setTokenURI] method comes from: we use it to store an item's metadata.

Also note that, unlike ERC-20, ERC-721 lacks a decimals field, since each token is distinct and cannot be partitioned.

New items can be created:

[source,javascript]

gameItem.awardItem(playerAddress, "https://game.example/item-id-8u5h2m.json")
Transaction successful. Transaction hash: 0x...
Events emitted:

  • Transfer(0x0000000000000000000000000000000000000000, playerAddress, 7)

And the owner and metadata of each item queried:

[source,javascript]

gameItem.ownerOf(7)
playerAddress
gameItem.tokenURI(7)
"https://game.example/item-id-8u5h2m.json"


This tokenURI should resolve to a JSON document that might look something like:

[source,json]

{
"name": "Thor's hammer",
"description": "Mjölnir, the legendary hammer of the Norse god of thunder.",
"image": "https://game.example/item-id-8u5h2m.png",
"strength": 20

}

For more information about the tokenURI metadata JSON Schema, check out the https://eips.ethereum.org/EIPS/eip-721[ERC-721 specification].

NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game!

TIP: If you'd like to put all item information on-chain, you can extend ERC-721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[Base64] Data URI with the JSON schema encoded. You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide.


Modules ROOT Pages Extending Contracts (`docs/modules/ROOT/pages/extending-contracts.adoc`)

= Extending Contracts

Most of the OpenZeppelin Contracts are expected to be used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance]: you will inherit from them when writing your own contracts.

This is the commonly found is syntax, like in contract MyToken is ERC20.

[NOTE]

Unlike contracts, Solidity librarys are not inherited from and instead rely on the https://solidity.readthedocs.io/en/latest/contracts.html#using-for[`using for`] syntax.

OpenZeppelin Contracts has some ``library``s: most are in the xref:api:utils.adoc[Utils] directory.

== Overriding

Inheritance is often used to add the parent contract's functionality to your own contract, but that's not all it can do. You can also change how some parts of the parent behave using overrides.

For example, imagine you want to change xref:api:access.adoc#AccessControl[AccessControl] so that xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[revokeRole] can no longer be called. This can be achieved using overrides:

solidity
include::api:example$access-control/AccessControlModified.sol[]

The old revokeRole is then replaced by our override, and any calls to it will immediately revert. We cannot remove the function from the contract, but reverting on all calls is good enough.

=== Calling super

Sometimes you want to extend a parent's behavior, instead of outright changing it to something else. This is where super comes in.

The super keyword will let you call functions defined in a parent contract, even if they are overridden. This mechanism can be used to add additional checks to a function, emit events, or otherwise add functionality as you see fit.

TIP: For more information on how overrides work, head over to the https://solidity.readthedocs.io/en/latest/contracts.html#index-17[official Solidity documentation].

Here is a modified version of xref:api:access.adoc#AccessControl[AccessControl] where xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[revokeRole] cannot be used to revoke the DEFAULT_ADMIN_ROLE:

solidity
// contracts/AccessControlNonRevokableAdmin.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {AccessControl} from "../../../access/AccessControl.sol";

contract AccessControlNonRevokableAdmin is AccessControl {
    error AccessControlNonRevokable();

    function revokeRole(bytes32 role, address account) public override {
        if (role == DEFAULT_ADMIN_ROLE) {
            revert AccessControlNonRevokable();
        }

        super.revokeRole(role, account);
    }
}

The super.revokeRole statement at the end will invoke AccessControl's original version of revokeRole, the same code that would've run if there were no overrides in place.

NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessControlDefaultAdminRules[AccessControlDefaultAdminRules], an extension that also adds enforced security measures for the DEFAULT_ADMIN_ROLE.

=== Token Compatibility Assumptions

Most token standards are written without considering non-standard transfer mechanisms like fees, rebases, or hooks that mutate balances outside of explicit transfers. This is a well-understood community convention: the standards specify a behavior, and integrations across the ecosystem rely on it. Contracts in this library follow the same convention. In particular, they assume that:

  • A successful xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer] or xref:api:token/ERC20.adoc#IERC20-transferFrom-address-address-uint256-[transferFrom] of amount reduces the sender's xref:api:token/ERC20.adoc#IERC20-balanceOf-address-[balanceOf] and increases the recipient's xref:api:token/ERC20.adoc#IERC20-balanceOf-address-[balanceOf] by exactly amount.
  • An account's xref:api:token/ERC20.adoc#IERC20-balanceOf-address-[balanceOf] only changes through explicit transfers, mints, or burns.

Considering many ERCs are not compatible with these mechanisms, the library has taken a policy of not accounting for tokens that break these assumptions, such as fee-on-transfer and rebasing variants, since they could desynchronize a contract's internal accounting from its on-chain balance. The resulting undercollateralization may cause later withdrawals, redemptions, or releases to revert.

TIP: When a contract custodies tokens and may accumulate excess balance (positive rebasing, accidental transfers, airdrops), consider exposing an access-controlled recovery function similar to xref:api:token/ERC20.adoc#ERC20Wrapper-_recover-address-[ERC20Wrapper._recover] to retrieve the surplus. This only helps when the contract holds more than it owes, not the undercollateralization case above.

== Security

The maintainers of OpenZeppelin Contracts are mainly concerned with the correctness and security of the code as published in the library, and the combinations of base contracts with the official extensions from the library.

Custom overrides, especially to hooks, can disrupt important assumptions and may introduce security risks in the code that was previously secure. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing to fully understand their impact and guarantee their security.

The way functions interact internally should not be assumed to stay stable across releases of the library. For example, a function that is used in one context in a particular release may not be used in the same context in the next release. Contracts that override functions should revalidate their assumptions when updating the version of OpenZeppelin Contracts they are built on.


Modules ROOT Pages Faq (`docs/modules/ROOT/pages/faq.adoc`)

= Frequently Asked Questions

== Can I restrict a function to EOAs only?

When calling external addresses from your contract it is unsafe to assume that an address is an externally-owned account (EOA) and not a contract. Attempting to prevent calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract constructor.

Although checking that the address has code, address.code.length > 0, may seem to differentiate contracts from EOAs, it can only say that an address is currently a contract, and its negation (that an address is not currently a contract) does not imply that the address is an EOA. Some counterexamples are:

  • address of a contract in construction
  • address where a contract will be created
  • address where a contract lived, but was destroyed

Furthermore, an address will be considered a contract within the same transaction where it is scheduled for destruction by SELFDESTRUCT, which only has an effect at the end of the entire transaction.


Modules ROOT Pages Governance (`docs/modules/ROOT/pages/governance.adoc`)

= How to set up on-chain governance

In this guide we will learn how OpenZeppelin’s Governor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally.

NOTE: Find detailed contract documentation at xref:api:governance.adoc[Governance API].

== Introduction

Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc.

This governance protocol is generally implemented in a special-purpose contract called “Governor”. The GovernorAlpha and GovernorBravo contracts designed by Compound have been very successful and popular so far, with the downside that projects with different requirements have had to fork the code to customize it for their needs, which can pose a high risk of introducing security issues. For OpenZeppelin Contracts, we set out to build a modular system of Governor contracts so that forking is not needed, and different requirements can be accommodated by writing small modules using Solidity inheritance. You will find the most common requirements out of the box in OpenZeppelin Contracts, but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. Additionally, the design of OpenZeppelin Governor requires minimal use of storage and results in more gas efficient operation.

== Compatibility

OpenZeppelin’s Governor system was designed with a concern for compatibility with existing systems that were based on Compound’s GovernorAlpha and GovernorBravo. Because of this, you will find that many modules are presented in two variants, one of which is built for compatibility with those systems.

=== ERC20Votes & ERC20VotesComp

The ERC-20 extension to keep track of votes and vote delegation is one such case. The shorter one is the more generic version because it can support token supplies greater than 2^96, while the “Comp” variant is limited in that regard, but exactly fits the interface of the COMP token that is used by GovernorAlpha and Bravo. Both contract variants share the same events, so they are fully compatible when looking at events only.

=== Governor & GovernorStorage

An OpenZeppelin Governor contract is not interface-compatible with Compound's GovernorAlpha or Bravo. Even though events are fully compatible, proposal lifecycle functions (creation, execution, etc.) have different signatures that are meant to optimize storage use. Other functions from GovernorAlpha and Bravo are likewise not available. It’s possible to opt in some Bravo-like behavior by inheriting from the GovernorStorage module. This module provides proposal enumerability and alternate versions of the queue, execute and cancel function that only take the proposal id. This module reduces the calldata needed by some operations in exchange for an increased storage footprint. This might be a good trade-off for some L2 chains. It also provides primitives for indexer-free frontends.

Note that even with the use of this module, one important difference with Compound's GovernorBravo is the way that proposalIds are calculated. Governor uses the hash of the proposal parameters with the purpose of keeping its data off-chain by event indexing, while the original Bravo implementation uses sequential proposalIds.

=== GovernorTimelockControl & GovernorTimelockCompound

When using a timelock with your Governor contract, you can use either OpenZeppelin’s TimelockController or Compound’s Timelock. Based on the choice of timelock, you should choose the corresponding Governor module: GovernorTimelockControl or GovernorTimelockCompound respectively. This allows you to migrate an existing GovernorAlpha instance to an OpenZeppelin-based Governor without changing the timelock in use.

=== Tally

https://www.tally.xyz[Tally] is a full-fledged application for user owned on-chain governance. It comprises a voting dashboard, proposal creation wizard, real time research and analysis, and educational content.

For all of these options, the Governor will be compatible with Tally: users will be able to create proposals, see voting periods and delays following xref:api:interfaces.adoc#IERC6372[IERC6372], visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use xref:defender::module/transaction-proposals.adoc[Defender Transaction Proposals] as an alternative interface.

In the rest of this guide, we will focus on a fresh deploy of the vanilla OpenZeppelin Governor features without concern for compatibility with GovernorAlpha or Bravo.

== Setup

=== Token

The voting power of each account in our governance setup will be determined by an ERC-20 token. The token has to implement the ERC20Votes extension. This extension will keep track of historical balances so that voting power is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting.

solidity
include::api:example$governance/MyToken.sol[]

If your project already has a live token that does not include ERC20Votes and is not upgradeable, you can wrap it in a governance token by using ERC20Wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1.

solidity
include::api:example$governance/MyTokenWrapped.sol[]

NOTE: The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[ERC721Votes]. ERC-721 tokens that don't provide this functionality can be wrapped into a voting tokens using a combination of xref:api:token/ERC721.adoc#ERC721Votes[ERC721Votes] and xref:api:token/ERC721.adoc#ERC721Wrapper[ERC721Wrapper].

NOTE: The internal clock used by the token to store voting balances will dictate the operating mode of the Governor contract attached to it. By default, block numbers are used. Since v4.9, developers can override the xref:api:interfaces.adoc#IERC6372[IERC6372] clock to use timestamps instead of block numbers.

=== Governor

Initially, we will build a Governor without a timelock. The core logic is given by the Governor contract, but we still need to choose: 1) how voting power is determined, 2) how many votes are needed for quorum, 3) what options people have when casting a vote and how those votes are counted, and 4) what type of token should be used to vote. Each of these aspects is customizable by writing your own module, or more easily choosing one from OpenZeppelin Contracts.

For 1) we will use the GovernorVotes module, which hooks to an IVotes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token. This module also discovers the clock mode (ERC-6372) used by the token and applies it to the Governor.

For 2) we will use GovernorVotesQuorumFraction which works together with ERC20Votes to define quorum as a percentage of the total supply at the block a proposal’s voting power is retrieved. This requires a constructor parameter to set the percentage. Most Governors nowadays use 4%, so we will initialize the module with parameter 4 (this indicates the percentage, resulting in 4%).

For 3) we will use GovernorCountingSimple, a module that offers 3 options to voters: For, Against, and Abstain, and where only For and Abstain votes are counted towards quorum.

Besides these modules, Governor itself has some parameters we must set.

votingDelay: How long after a proposal is created should voting power be fixed. A large voting delay gives users time to unstake tokens if necessary.

votingPeriod: How long does a proposal remain open to votes.

These parameters are specified in the unit defined in the token's clock. Assuming the token uses block numbers, and assuming block time of around 12 seconds, we will have set votingDelay = 1 day = 7200 blocks, and votingPeriod = 1 week = 50400 blocks.

We can optionally set a proposal threshold as well. This restricts proposal creation to accounts that have enough voting power.

solidity
include::api:example$governance/MyGovernor.sol[]

=== Timelock

It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the GovernorTimelockControl module.

IMPORTANT: When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. Before version 4.5 there was no way to recover funds in the Governor contract when using a timelock! Before version 4.3, when using the Compound Timelock, ETH in the timelock was not easily accessible.

TimelockController uses an AccessControl setup that we need to understand in order to set up roles.

  • The Proposer role is in charge of queueing operations: this is the role the Governor instance should be granted, and it should likely be the only proposer in the system.
  • The Executor role is in charge of executing already available operations: we can assign this role to the special zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead).
  • The Canceller role can cancel queued operations in the timelock: the Governor should hold this role for proposal cancellations to work after queuing. If the Governor is set as a proposer at deployment, it will automatically receive the Canceller role as well.
  • Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role.

WARNING: Granting additional proposers or cancellers besides the Governor is risky. They can execute operations as the timelock (bypassing governance) and cancel approved proposals, potentially causing a denial of service attack against governance.

== Proposal Lifecycle

Let’s walk through how to create and execute a proposal on our newly deployed Governor.

A proposal is a sequence of actions that the Governor contract will perform if it passes. Each action consists of a target address, calldata encoding a function call, and an amount of ETH to include. Additionally, a proposal includes a human-readable description.

=== Create a Proposal

Let’s say we want to create a proposal to give a team a grant, in the form of ERC-20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC-20 token, calldata is the encoded function call transfer(<team wallet>, <grant amount>), and with 0 ETH attached.

Generally a proposal will be created with the help of an interface such as Tally or xref:defender::module/transaction-proposals.adoc[Defender Proposals]. Here we will show how to create the proposal using Ethers.js.

First we get all the parameters necessary for the proposal action.

javascript
const tokenAddress = ...;
const token = await ethers.getContractAt("ERC20", tokenAddress);

const teamAddress = ...;
const grantAmount = ...;
const transferCalldata = token.interface.encodeFunctionData("transfer", [teamAddress, grantAmount]);

Now we are ready to call the propose function of the Governor. Note that we don’t pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case it’s a single action, so it’s simple:

javascript
await governor.propose(
  [tokenAddress],
  [0],
  [transferCalldata],
  "Proposal #1: Give grant to team",
);

This will create a new proposal, with a proposal id that is obtained by hashing together the proposal data, and which will also be found in an event in the logs of the transaction.

=== Cast a Vote

Once a proposal is active, delegates can cast their vote. Note that it is delegates who carry voting power: if a token holder wants to participate, they can set a trusted representative as their delegate, or they can become a delegate themselves by self-delegating their voting power.

Votes are cast by interacting with the Governor contract through the castVote family of functions. Voters would generally invoke this from a governance UI such as Tally.

image::tally-vote.png[Voting in Tally]

=== Execute the Proposal

Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. Once a proposal passes, it can be queued and executed from the same place you voted.

image::tally-exec.png[Administration Panel in Tally]

We will see now how to do this manually using Ethers.js.

If a timelock was set up, the first step to execution is queueing. You will notice that both the queue and execute functions require passing in the entire proposal parameters, as opposed to just the proposal id. This is necessary because this data is not stored on chain, as a measure to save gas. Note that these parameters can always be found in the events emitted by the contract. The only parameter that is not sent in its entirety is the description, since this is only needed in its hashed form to compute the proposal id.

To queue, we call the queue function:

javascript
const descriptionHash = ethers.id("Proposal #1: Give grant to team");

await governor.queue(
  [tokenAddress],
  [0],
  [transferCalldata],
  descriptionHash,
);

This will cause the Governor to interact with the timelock contract and queue the actions for execution after the required delay.

After enough time has passed (according to the timelock parameters), the proposal can be executed. If there was no timelock to begin with, this step can be run immediately after the proposal succeeds.

javascript
await governor.execute(
  [tokenAddress],
  [0],
  [transferCalldata],
  descriptionHash,
);

Executing the proposal will transfer the ERC-20 tokens to the chosen recipient. To wrap up: we set up a system where a treasury is controlled by the collective decision of the token holders of a project, and all actions are executed via proposals enforced by on-chain votes.

== Timestamp based governance

=== Motivation

It is sometimes difficult to deal with durations expressed in number of blocks because of inconsistent or unpredictable time between blocks. This is particularly true of some L2 networks where blocks are produced based on blockchain usage. Using number of blocks can also lead to the governance rules being affected by network upgrades that modify the expected time between blocks.

The difficulty of replacing block numbers with timestamps is that the Governor and the token must both use the same format when querying past votes. If a token is designed around block numbers, it is not possible for a Governor to reliably do timestamp based lookups.

Therefore, designing a timestamp based voting system starts with the token.

=== Token

Since v4.9, all voting contracts (including xref:api:token/ERC20.adoc#ERC20Votes[ERC20Votes] and xref:api:token/ERC721.adoc#ERC721Votes[ERC721Votes]) rely on xref:api:interfaces.adoc#IERC6372[IERC6372] for clock management. In order to change from operating with block numbers to operating with timestamps, all that is required is to override the clock() and CLOCK_MODE() functions.

solidity
include::api:example$governance/MyTokenTimestampBased.sol[]

=== Governor

The Governor will automatically detect the clock mode used by the token and adapt to it. There is no need to override anything in the Governor contract. However, the clock mode does affect how some values are interpreted. It is therefore necessary to set the votingDelay() and votingPeriod() accordingly.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";

contract MyGovernor is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
    constructor(IVotes _token, TimelockController _timelock)
        Governor("MyGovernor")
        GovernorVotes(_token)
        GovernorVotesQuorumFraction(4)
        GovernorTimelockControl(_timelock)
    {}

    function votingDelay() public pure virtual override returns (uint256) {
        return 1 days;
    }

    function votingPeriod() public pure virtual override returns (uint256) {
        return 1 weeks;
    }

    function proposalThreshold() public pure virtual override returns (uint256) {
        return 0;
    }

    // ...
}

=== Disclaimer

Timestamp based voting is a recent feature that was formalized in ERC-6372 and ERC-5805, and introduced in v4.9. At the time this feature is released, some governance tooling may not support it yet. Users can expect invalid reporting of deadlines & durations if the tool is not able to interpret the ERC6372 clock. This invalid reporting by offchain tools does not affect the onchain security and functionality of the governance contract.

Governors with timestamp support (v4.9 and above) are compatible with old tokens (before v4.9) and will operate in "block number" mode (which is the mode all old tokens operate on). On the other hand, old Governor instances (before v4.9) are not compatible with new tokens operating using timestamps. If you update your token code to use timestamps, make sure to also update your Governor code.


Modules ROOT Pages Index (`docs/modules/ROOT/pages/index.adoc`)

= Contracts

A library for secure smart contract development. Build on a solid foundation of community-vetted code.

  • Implementations of standards like xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721].
  • Flexible xref:access-control.adoc[role-based permissioning] scheme.
  • Reusable xref:utilities.adoc[Solidity components] to build custom contracts and complex decentralized systems.

IMPORTANT: OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. For upgradeable contracts, the storage layout of different major versions should be assumed incompatible, for example, it is unsafe to upgrade from 4.9.3 to 5.0.0. Learn more at xref:backwards-compatibility.adoc[Backwards Compatibility].

== Overview

[[release-tags]]
=== Release Tags

We use NPM tags to clearly distinguish between audited and non-audited versions of our package:

[%header,cols=3*]
|===
| Tag
| Purpose
| Description

| latest
| ✅ Audited releases
| Stable, audited versions of the package. This is the default version installed when users run npm install @openzeppelin/contracts.

| dev
| 🧪 Final but not audited
| Versions that are finalized and feature-complete but have not yet been audited. This version is fully tested, can be used in production and is covered by the bug bounty.

| next
| 🚧 Release candidates
| Pre-release versions that are not final. Used for testing and validation before the version becomes a final dev or latest release.
|===

[[install]]
=== Installation

==== Hardhat (npm)

console
$ npm install @openzeppelin/contracts

→ Installs the latest audited release (latest).

text
$ npm install @openzeppelin/contracts@dev

→ Installs the latest unaudited release (dev).

==== Foundry (git)

WARNING: When installing via git, it is a common error to use the master branch. This is a development branch that should be avoided in favor of tagged releases. The release process involves security measures that the master branch does not guarantee.

WARNING: Foundry installs the latest version initially, but subsequent forge update commands will use the master branch.

console
$ forge install OpenZeppelin/openzeppelin-contracts

Add @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ in remappings.txt.

[[usage]]
=== Usage

Once installed, you can use the contracts in the library by importing them:

[source,solidity]

include::api:example$MyNFT.sol[]

TIP: If you're new to smart contract development, head to xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] to learn about creating a new project and compiling your contracts.

To keep your system secure, you should always use the installed code as-is, and neither copy-paste it from online sources, nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs.

[[security]]
== Security

Please report any security issues you find via our https://www.immunefi.com/bounty/openzeppelin[bug bounty program on Immunefi] or directly to [email protected].

The https://contracts.openzeppelin.com/security[Security Center] contains more details about the secure development process.

[[next-steps]]
== Learn More

The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides:

  • xref:access-control.adoc[Access Control]: decide who can perform each of the actions on your system.
  • xref:tokens.adoc[Tokens]: create tradable assets or collectibles, like the well known xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721] standards.
  • xref:utilities.adoc[Utilities]: generic useful tools, including non-overflowing math, signature verification, and trustless paying systems.

The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the https://forum.openzeppelin.com[community forum].

The following articles provide great background reading, though please note, some of the referenced tools have changed as the tooling in the ecosystem continues to rapidly evolve.


Modules ROOT Pages Multisig (`docs/modules/ROOT/pages/multisig.adoc`)

= 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/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/contracts/utils/cryptography/signers/draft-ERC7739.sol";
import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {SignerERC7913} from "@openzeppelin/contracts/utils/cryptography/signers/SignerERC7913.sol";

contract MyAccountERC7913 is Account, EIP712, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
constructor() EIP712("MyAccount7913", "1") {}

text
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/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/contracts/utils/cryptography/signers/draft-ERC7739.sol";
import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {MultiSignerERC7913} from "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913.sol";

contract MyAccountMultiSigner is
Account,
EIP712,
MultiSignerERC7913,
ERC7739,
ERC7821,
ERC721Holder,
ERC1155Holder,
Initializable
{
constructor() EIP712("MyAccountMultiSigner", "1") {}

text
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/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/contracts/utils/cryptography/signers/draft-ERC7739.sol";
import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {MultiSignerERC7913Weighted} from "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol";

contract MyAccountMultiSignerWeighted is
Account,
EIP712,
MultiSignerERC7913Weighted,
ERC7739,
ERC7821,
ERC721Holder,
ERC1155Holder,
Initializable
{
constructor() EIP712("MyAccountMultiSignerWeighted", "1") {}

text
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)

text
// 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;
signers[0] = ecdsaSigner;
signers[1] = p256Signer;
signers[2] = rsaSigner;

text
// 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.


Modules ROOT Pages Paymasters (`docs/modules/ROOT/pages/paymasters.adoc`)

= Paymasters

In case you want to sponsor user operations for your users, ERC-4337 defines a special type of contract called paymaster, whose purpose is to pay the gas fees consumed by the user operation.

In the context of account abstraction, sponsoring user operations allows a third party to pay for transaction gas fees on behalf of users. This can improve user experience by eliminating the need for users to hold native cryptocurrency (like ETH) to pay for transactions.

To enable sponsorship, users sign their user operations including a special field called paymasterAndData, resulting from the concatenation of the paymaster address they're intending to use and the associated calldata that's going to be passed into xref:api:utils/cryptography.adoc#Paymaster-validatePaymasterUserOp[validatePaymasterUserOp]. The EntryPoint will use this field to determine whether it is willing to pay for the user operation or not.

== Signed Sponsorship

The xref:api:account.adoc#PaymasterSigner[PaymasterSigner] implements signature-based sponsorship via authorization signatures, allowing designated paymaster signers to authorize and sponsor specific user operations without requiring users to hold native ETH.

TIP: Learn more about xref:accounts.adoc#selecting-a-signer[signers] to explore different approaches to user operation sponsorship via signatures.

[source,solidity]

include::api:example$account/paymaster/PaymasterECDSASigner.sol[]

TIP: xref:api:account.adoc#ERC4337Utils[ERC4337Utils] to facilitate the access to paymaster-related fields of the userOp (e.g. paymasterData, paymasterVerificationGasLimit)

To implement signature-based sponsorship, you'll first need to deploy the paymaster contract. This contract will hold the ETH used to pay for user operations and verify signatures from your authorized signer. After deployment, you must fund the paymaster with ETH to cover gas costs for the operations it will sponsor:

[source,typescript]

// Fund the paymaster with ETH
await eoaClient.sendTransaction({
to: paymasterECDSASigner.address,
value: parseEther("0.01"),
data: encodeFunctionData({
abi: paymasterECDSASigner.abi,
functionName: "deposit",
args: [],
}),

});

WARNING: Paymasters require sufficient ETH balance to pay for gas costs. If the paymaster runs out of funds, all operations it's meant to sponsor will fail. Consider implementing monitoring and automatic refilling of the paymaster's balance in production environments.

When a user initiates an operation that requires sponsorship, your backend service (or other authorized entity) needs to sign the operation using EIP-712. This signature proves to the paymaster that it should cover the gas costs for this specific user operation:

[source,typescript]

// Set validation window
const now = Math.floor(Date.now() / 1000);
const validAfter = now - 60; // Valid from 1 minute ago
const validUntil = now + 3600; // Valid for 1 hour
const paymasterVerificationGasLimit = 100_000n;
const paymasterPostOpGasLimit = 300_000n;

// Sign using EIP-712 typed data
// NOTE: initCode below is the effective initCode (see effectiveInitCode further down), not
// userOp.initCode. The two only differ for EIP-7702 senders.
const paymasterSignature = await signer.signTypedData({
domain: {
chainId: await signerClient.getChainId(),
name: "MyPaymasterECDSASigner",
verifyingContract: paymasterECDSASigner.address,
version: "1",
},
types: {
UserOperationRequest: [
{ 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: "paymasterVerificationGasLimit", type: "uint256" },
{ name: "paymasterPostOpGasLimit", type: "uint256" },
{ name: "validAfter", type: "uint48" },
{ name: "validUntil", type: "uint48" },
],
},
primaryType: "UserOperationRequest",
message: {
sender: userOp.sender,
nonce: userOp.nonce,
initCode: await effectiveInitCode(publicClient, userOp),
callData: userOp.callData,
accountGasLimits: userOp.accountGasLimits,
preVerificationGas: userOp.preVerificationGas,
gasFees: userOp.gasFees,
paymasterVerificationGasLimit,
paymasterPostOpGasLimit,
validAfter,
validUntil,
},

});

[[eip-7702-init-code]]
=== Signing for EIP-7702 senders

The initCode field of the signed struct is not always userOp.initCode. For EIP-7702 senders — those whose initCode is the 20-byte 0x7702 marker (the two marker bytes followed by 18 zero bytes) — the paymaster substitutes the delegate currently installed at userOp.sender, mirroring what the EntryPoint does when it computes userOpHash. Signing the raw marker instead would produce a digest the paymaster does not recognize, and the operation would be rejected.

Compute the value to sign with a helper:

[source,typescript]

import { concat, padHex, size, slice } from "viem";

const EIP7702_INIT_CODE_MARKER = padHex("0x7702", { size: 20, dir: "right" });

async function effectiveInitCode(publicClient, userOp) {
const initCode = userOp.initCode ?? "0x";

// The marker is compared over the full 20 bytes, so bytes 2..19 must be zero (a shorter initCode is
// zero-padded). Anything else is a regular factory deployment and is signed as-is — including a
// factory address that merely happens to start with 0x7702.
const head = padHex(slice(initCode, 0, Math.min(20, size(initCode))), { size: 20, dir: "right" });
if (head !== EIP7702_INIT_CODE_MARKER) return initCode;

// Read the delegate out of the sender's code, stripping the 0xef0100 EIP-7702 prefix
const code = await publicClient.getCode({ address: userOp.sender });
const delegate = slice(code, 3, 23);

return size(initCode) > 20 ? concat([delegate, slice(initCode, 20)]) : delegate; }

WARNING: Because the delegate is read at validation time, a sponsorship signature issued while one delegate is installed stops being valid once the account owner replaces it. This is deliberate: it prevents a signature obtained for one delegate's behavior from authorizing an operation executed by another. Signing services must therefore read the sender's code when signing rather than caching a digest.

The time window (validAfter and validUntil) allows you to limit how long the signature remains valid. Once signed, the paymaster data needs to be formatted and attached to the user operation:

[source,typescript]

userOp.paymasterAndData = encodePacked(
["address", "uint128", "uint128", "bytes"],
[
paymasterECDSASigner.address,
paymasterVerificationGasLimit,
paymasterPostOpGasLimit,
encodePacked(
["uint48", "uint48", "bytes"],
[validAfter, validUntil, paymasterSignature]
),
]

);

TIP: The paymasterVerificationGasLimit and paymasterPostOpGasLimit values should be adjusted based on your paymaster's complexity. Higher values increase the gas cost but provide more execution headroom, reducing the risk of out-of-gas errors during validation or post-operation processing.

With the paymaster data attached, the user operation can now be signed by the account signer and submitted to the EntryPoint contract:

[source,typescript]

// Sign the user operation with the account owner
const signedUserOp = await signUserOp(entrypoint, userOp);

// Submit to the EntryPoint contract const userOpReceipt = await eoaClient.writeContract({ abi: EntrypointV09Abi, address: entrypoint.address, functionName: "handleOps", args: [[signedUserOp], beneficiary.address], });

Behind the scenes, the EntryPoint will call the paymaster's validatePaymasterUserOp function, which verifies the signature and time window. If valid, the paymaster commits to paying for the operation's gas costs, and the EntryPoint executes the operation.

== ERC20-based Sponsorship

While signature-based sponsorship is useful for many applications, sometimes you want users to pay for their own transactions but using tokens instead of ETH. The xref:api:account.adoc#PaymasterERC20[PaymasterERC20] allows users to pay for gas fees using ERC-20 tokens. Developers must implement an xref:api:account.adoc#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-[_fetchDetails] to get the token price information from an oracle of their preference.

[source,solidity]

function _fetchDetails(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal view override returns (uint256 validationData, IERC20 token, uint256 tokenPerNative) {
// Implement logic to fetch the token and token price from the userOp

}

=== Using Oracles

==== Chainlink Price Feeds

A popular approach to implement price oracles is to use https://docs.chain.link/data-feeds/using-data-feeds[Chainlink's price feeds]. By using their https://docs.chain.link/data-feeds/api-reference#aggregatorv3interface[`AggregatorV3Interface`] developers determine the token-to-ETH exchange rate dynamically for their paymasters. This ensures fair pricing even as market rates fluctuate.

Consider the following contract:

[source,solidity]

// WARNING: Unaudited code.
// Consider performing a security review before going to production.
contract PaymasterUSDCChainlink is PaymasterERC20, Ownable {
// Values for sepolia
// See https://docs.chain.link/data-feeds/price-feeds/addresses
AggregatorV3Interface public constant USDC_USD_ORACLE =
AggregatorV3Interface(0xA2F78ab2355fe2f984D808B5CeE7FD0A93D5270E);
AggregatorV3Interface public constant ETH_USD_ORACLE =
AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);

text
// See https://sepolia.etherscan.io/token/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
IERC20 private constant USDC =
    IERC20(0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238);

constructor(address initialOwner) Ownable(initialOwner) {}

function _authorizeWithdraw() internal virtual override onlyOwner {}

function liveness() public view virtual returns (uint256) {
    return 15 minutes; // Tolerate stale data
}

/// @dev Floor: refuse to sponsor when 1 ETH is worth less than 100 USDC.
/// 1e8 = 100 (USDC per ETH) * 10**6 (USDC decimals); the 1e18 denom cancels 1e18 wei/ETH.
function _minTokensPerNative() internal view virtual override returns (uint256) {
    return 1e8;
}

function _fetchDetails(
    PackedUserOperation calldata userOp,
    bytes32 /* userOpHash */
) internal view virtual override returns (uint256 validationData, IERC20 token, uint256 tokenPerNative) {
    (uint256 validationData_, uint256 price) = _fetchOracleDetails(userOp);
    return (
        validationData_,
        USDC,
        price
    );
}

function _fetchOracleDetails(
    PackedUserOperation calldata /* userOp */
)
    internal
    view
    virtual
    returns (uint256 validationData, uint256 tokenPerNative)
{
  // ...
}

}

NOTE: The PaymasterUSDCChainlink contract uses specific Chainlink price feeds (ETH/USD and USDC/USD) on Sepolia. For production use or other networks, you'll need to modify the contract to use the appropriate price feed addresses.

NOTE: xref:api:account.adoc#PaymasterERC20-_minTokensPerNative--[_minTokensPerNative] defaults to 0 in PaymasterERC20 and should be overridden. It is a lower bound on tokenPerNative (same units: token-units per native coin, scaled by xref:api:account.adoc#PaymasterERC20-_tokenPerNativeDenominator--[_tokenPerNativeDenominator]); any operation whose _fetchDetails returns a tokenPerNative strictly below this floor is rejected with SIG_VALIDATION_FAILED before pre-charging the user. The example above returns 1e8, which refuses to sponsor when 1 ETH is worth less than 100 USDC — a conservative sanity check against misconfigured oracles or extreme market conditions. Returning 0 disables the check and re-opens the "free sponsorship" footgun if _fetchDetails ever returns a near-zero price.

As you can see, a _fetchOracleDetails function is specified to fetch the token price that will be used as a reference for calculating the final ERC-20 payment. One can fetch and process price data from Chainlink oracles to determine the exchange rate between ETH and a concrete ERC-20. An example with USDC would be:

  1. Fetch the current ETH/USD and USDC/USD prices from their respective oracles.
  2. tokenPerNative is the number of USDC units (smallest denomination) charged per native token. Combining the two feeds: tokenPerNative = (ETH_USD / USDC_USD) * 10**USDC_decimals. Note that the oracles may come with their own denominator that has to be considered.

NOTE: On mainnet tokenPerNative is expressed per ETH and not per wei. On most EVM chains, this native token includes 10¹⁸ units (wei). This can be customized by overriding xref:api:account.adoc#PaymasterERC20-_tokenPerNativeDenominator--[_tokenPerNativeDenominator].

Here's how an implementation of _fetchOracleDetails would look like using this approach:

TIP: Use xref:api:account.adoc#ERC4337Utils-combineValidationData-uint256-uint256-[ERC4337Utils.combineValidationData] to merge two validationData values.

[source,solidity]

// WARNING: Unaudited code.
// Consider performing a security review before going to production.

using ERC4337Utils for *;

function _fetchOracleDetails(
PackedUserOperation calldata /* userOp */
)
internal
view
virtual
returns (uint256 validationData, uint256 tokenPerNative)
{
(uint256 ETHUSDValidationData, uint256 ETHUSD) = _fetchPrice(ETH_USD_ORACLE);
(uint256 USDCUSDValidationData, uint256 USDCUSD) = _fetchPrice(USDC_USD_ORACLE);

text
// USDC_units / eth = (ETH/USD) / (USDC/USD) * 10 ** USDC_decimals
return (
    ETHUSDValidationData.combineValidationData(USDCUSDValidationData),
    ETHUSD
        * (10 ** USDC.decimals())
        * (10 ** USDC_USD_ORACLE.decimals())
        / USDCUSD
        / (10 ** ETH_USD_ORACLE.decimals())
);

}

function fetchPrice(
AggregatorV3Interface oracle
) internal view virtual returns (uint256 validationData, uint256 price) {
(
uint80 roundId,
int256 price
,
,
uint256 timestamp,
uint80 answeredInRound
) = oracle.latestRoundData();
if (
price_ <= 0 || // No data or negative price
answeredInRound < roundId || // Not answered in round
timestamp == 0 || // Incomplete round
block.timestamp - timestamp > liveness() // Stale data
) {
return (ERC4337Utils.SIG_VALIDATION_FAILED, 0);
}
return (ERC4337Utils.SIG_VALIDATION_SUCCESS, uint256(price_));

}

NOTE: An important difference with token-based sponsorship is that the user's smart account must first approve the paymaster to spend their tokens. You might want to incorporate this approval as part of your account initialization process, or check if approval is needed before executing an operation.

The PaymasterERC20 contract follows a pre-charge and refund model:

  1. During validation, it pre-charges the maximum possible gas cost
  2. After execution, it refunds any unused gas back to the user

This model ensures the paymaster can always cover gas costs, while only charging users for the actual gas used.

[source,typescript]

const paymasterVerificationGasLimit = 150_000n;
const paymasterPostOpGasLimit = 300_000n;

userOp.paymasterAndData = encodePacked(
["address", "uint128", "uint128", "bytes"],
[
paymasterUSDCChainlink.address,
paymasterVerificationGasLimit,
paymasterPostOpGasLimit,
"0x" // No additional data needed
]

);

For the rest, you can sign the user operation as you would normally do once the paymasterAndData field has been set.

[source,typescript]

// Sign the user operation with the account owner
const signedUserOp = await signUserOp(entrypoint, userOp);

// Submit to the EntryPoint contract const userOpReceipt = await eoaClient.writeContract({ abi: EntrypointV09Abi, address: entrypoint.address, functionName: "handleOps", args: [[signedUserOp], beneficiary.address], });

WARNING: Oracle-based pricing relies on the accuracy and freshness of price feeds. The PaymasterUSDCChainlink includes safety checks for stale data, but you should still monitor for extreme market volatility that could affect your users.

=== Using a Guarantor

There are multiple valid cases where the user might not have enough tokens to pay for the transaction before it takes place. For example, if the user is claiming an airdrop, they might need their first transaction to be sponsored. For those cases, the xref:api:account.adoc#PaymasterERC20Guarantor[PaymasterERC20Guarantor] contract extends the standard PaymasterERC20 to allow a third party (guarantor) to back user operations.

The guarantor pre-funds the maximum possible gas cost upfront, and after execution:

  1. If the user repays the guarantor, the guarantor gets their funds back
  2. If the user fails to repay, the guarantor absorbs the cost

[TIP]

A common use case is for guarantors to pay for operations of users claiming airdrops:

  • The guarantor pays gas fees upfront
  • The user claims their airdrop tokens
  • The user repays the guarantor from the claimed tokens
  • If the user fails to repay, the guarantor absorbs the cost

To implement guarantor functionality, your paymaster needs to extend the PaymasterERC20Guarantor class and implement the _fetchGuarantor function:

[source,solidity]

function _fetchGuarantor(
PackedUserOperation calldata userOp
) internal view override returns (address guarantor) {
// Implement logic to fetch and validate the guarantor from userOp

}

Let's create a guarantor-enabled paymaster by extending our previous example:

[source,solidity]

// WARNING: Unaudited code.
// Consider performing a security review before going to production.
contract PaymasterUSDCGuaranteed is EIP712, PaymasterERC20Guarantor, Ownable {

text
// Keep the same oracle code as before...

bytes32 private constant GUARANTEED_USER_OPERATION_TYPEHASH =
    keccak256(
        "GuaranteedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,uint256 paymasterVerificationGasLimit,uint256 paymasterPostOpGasLimit,bytes paymasterData)"
    );

constructor(
    address initialOwner
) EIP712("PaymasterUSDCGuaranteed", "1") Ownable(initialOwner) {}

// Other functions from PaymasterUSDCChainlink...

function _fetchGuarantor(
    PackedUserOperation calldata userOp
) internal view override returns (address guarantor) {
    bytes calldata paymasterData = userOp.paymasterData();

    // If no guarantor specified, return early
    if (paymasterData.length < 20) {
        return address(0);
    }

    guarantor = address(bytes20(paymasterData));
    bytes calldata guarantorSignature = paymasterData[20:];

    // Validate the guarantor's signature
    bytes32 structHash = _getGuaranteedOperationStructHash(userOp);
    bytes32 hash = _hashTypedDataV4(structHash);

    return SignatureChecker.isValidSignatureNow(
        guarantor,
        hash,
        guarantorSignature
    ) ? guarantor : address(0);
}

function _getGuaranteedOperationStructHash(
    PackedUserOperation calldata userOp
) internal pure returns (bytes32) {
    return keccak256(
        abi.encode(
            GUARANTEED_USER_OPERATION_TYPEHASH,
            userOp.sender,
            userOp.nonce,
            keccak256(userOp.initCode),
            keccak256(userOp.callData),
            userOp.accountGasLimits,
            userOp.preVerificationGas,
            userOp.gasFees,
            // Guarantor explicitly signs the paymaster gas limits to prevent sender-controlled under-provisioning.
            userOp.paymasterVerificationGasLimit(),
            userOp.paymasterPostOpGasLimit(),
            keccak256(bytes(userOp.paymasterData()[:20])) // Just the guarantor address part
        )
    );
}

}

With this implementation, a guarantor would sign a user operation to authorize backing it:

[source,typescript]

// Sign the user operation with the guarantor
const guarantorSignature = await guarantor.signTypedData({
domain: {
chainId: await guarantorClient.getChainId(),
name: "PaymasterUSDCGuaranteed",
verifyingContract: paymasterUSDC.address,
version: "1",
},
types: {
GuaranteedUserOperation: [
{ 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: "paymasterData", type: "bytes" }
]
},
primaryType: "GuaranteedUserOperation",
message: {
sender: userOp.sender,
nonce: userOp.nonce,
initCode: userOp.initCode,
callData: userOp.callData,
accountGasLimits: userOp.accountGasLimits,
preVerificationGas: userOp.preVerificationGas,
gasFees: userOp.gasFees,
paymasterData: guarantorAddress // Just the guarantor address
},

});

Then, we include the guarantor's address and its signature in the paymaster data:

[source,typescript]

const paymasterVerificationGasLimit = 150_000n;
const paymasterPostOpGasLimit = 300_000n;

userOp.paymasterAndData = encodePacked(
["address", "uint128", "uint128", "bytes"],
[
paymasterUSDC.address,
paymasterVerificationGasLimit,
paymasterPostOpGasLimit,
encodePacked(
["address", "bytes"],
[guarantorAddress, guarantorSignature]
)
]

);

When the operation executes:

  1. During validation, the paymaster verifies the guarantor's signature and pre-funds from the guarantor's account
  2. The user operation executes, potentially giving the user tokens (like in an airdrop claim)
  3. During post-operation, the paymaster first tries to get repayment from the user
  4. If the user can't pay, the guarantor's pre-funded amount is used
  5. An event is emitted indicating who ultimately paid for the operation

This approach enables novel use cases where users don't need tokens to start using a web3 app, and can cover costs after receiving value through their transaction.

== Practical Considerations

When implementing paymasters in production environments, keep these considerations in mind:

  1. Balance management: Regularly monitor and replenish your paymaster's ETH balance to ensure uninterrupted service.

  2. Gas limits: The verification and post-operation gas limits should be set carefully. Too low, and operations might fail; too high, and you waste resources.

  3. Security: For signature-based paymasters, protect your signing key as it controls who gets subsidized operations.

  4. Price volatility: For token-based paymasters, consider restricting which tokens are accepted, and implementing circuit breakers for extreme market conditions.

  5. Spending limits: Consider implementing daily or per-user limits to prevent abuse of your paymaster.

TIP: For production deployments, it's often useful to implement a monitoring service that tracks paymaster usage, balances, and other metrics to ensure smooth operation.


Modules ROOT Pages Tokens (`docs/modules/ROOT/pages/tokens.adoc`)

= Tokens

Ah, the "token": blockchain's most powerful and most misunderstood tool.

A token is a representation of something in the blockchain. This something can be money, time, services, shares in a company, a virtual pet, anything. By representing things as tokens, we can allow smart contracts to interact with them, exchange them, create or destroy them.

[[but-first-coffee-a-primer-on-token-contracts]]
== But First, [strikethrough]#Coffee# a Primer on Token Contracts

Much of the confusion surrounding tokens comes from two concepts getting mixed up: token contracts and the actual tokens.

A token contract is simply an Ethereum smart contract. "Sending tokens" actually means "calling a method on a smart contract that someone wrote and deployed". At the end of the day, a token contract is not much more than a mapping of addresses to balances, plus some methods to add and subtract from those balances.

It is these balances that represent the tokens themselves. Someone "has tokens" when their balance in the token contract is non-zero. That's it! These balances could be considered money, experience points in a game, deeds of ownership, or voting rights, and each of these tokens would be stored in different token contracts.

[[different-kinds-of-tokens]]
== Different Kinds of Tokens

Note that there's a big difference between having two voting rights and two deeds of ownership: each vote is equal to all others, but houses usually are not! This is called https://en.wikipedia.org/wiki/Fungibility[fungibility]. Fungible goods are equivalent and interchangeable, like Ether, fiat currencies, and voting rights. Non-fungible goods are unique and distinct, like deeds of ownership, or collectibles.

In a nutshell, when dealing with non-fungibles (like your house) you care about which ones you have, while in fungible assets (like your bank account statement) what matters is how much you have.

== Standards

Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of standards (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts.

You've probably heard of the ERC-20 or ERC-721 token standards, and that's why you're here. Head to our specialized guides to learn more about these:

  • xref:erc20.adoc[ERC-20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity.
  • xref:erc721.adoc[ERC-721]: the de-facto solution for non-fungible tokens, often used for collectibles and games.
  • xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency.

Modules ROOT Pages Upgradeable (`docs/modules/ROOT/pages/upgradeable.adoc`)

= Using with Upgrades

If your contract is going to be deployed with upgradeability, such as using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins], you will need to use the Upgradeable variant of OpenZeppelin Contracts.

This variant is available as a separate package called @openzeppelin/contracts-upgradeable, which is hosted in the repository https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable[OpenZeppelin/openzeppelin-contracts-upgradeable]. It uses @openzeppelin/contracts as a peer dependency.

It follows all of the rules for xref:upgrades-plugins::writing-upgradeable.adoc[Writing Upgradeable Contracts]: constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions.

== Overview

=== Installation

console
$ npm install @openzeppelin/contracts-upgradeable @openzeppelin/contracts

=== Usage

The Upgradeable package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix Upgradeable.

diff
-import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
 
-contract MyCollectible is ERC721 {
+contract MyCollectible is ERC721Upgradeable {

NOTE: Interfaces and libraries are not included in the Upgradeable package, but are instead imported from the main OpenZeppelin Contracts package.

Constructors are replaced by internal initializer functions following the naming convention +__{ContractName}_init+. Since these are internal, you must always define your own public initializer function and call the parent initializer of the contract you extend.

diff
-    constructor() ERC721("MyCollectible", "MCO") {
+    function initialize() initializer public {
+        __ERC721_init("MyCollectible", "MCO");
     }

CAUTION: Use with multiple inheritance requires special attention. See the section below titled <>.

Once this contract is set up and compiled, you can deploy it using the xref:upgrades-plugins::index.adoc[Upgrades Plugins]. The following snippet shows an example deployment script using Hardhat.

js
// scripts/deploy-my-collectible.js
const { ethers, upgrades } = require("hardhat");

async function main() {
  const MyCollectible = await ethers.getContractFactory("MyCollectible");

  const mc = await upgrades.deployProxy(MyCollectible);

  await mc.waitForDeployment();
  console.log("MyCollectible deployed to:", await mc.getAddress());
}

main();

== Further Notes

[[multiple-inheritance]]
=== Multiple Inheritance

Initializer functions are not linearized by the compiler like constructors. Because of this, each +__{ContractName}_init+ function embeds the linearized calls to all parent initializers. As a consequence, calling two of these init functions can potentially initialize the same contract twice.

The function +__{ContractName}_init_unchained+ found in every contract is the initializer function minus the calls to parent initializers, and can be used to avoid the double initialization problem, but doing this manually is not recommended. We hope to be able to implement safety checks for this in future versions of the Upgrades Plugins.

=== Namespaced Storage

You may notice that contracts use a struct with the @custom:storage-location erc7201:<NAMESPACE_ID> annotation to store the contract's state variables. This follows the https://eips.ethereum.org/EIPS/eip-7201[ERC-7201: Namespaced Storage Layout] pattern, where each contract has its own storage layout in a namespace that is separate from other contracts in the inheritance chain.

Without namespaced storage, it isn't safe to simply add a state variable because it "shifts down" all of the state variables below in the inheritance chain. This makes the storage layouts incompatible, as explained in xref:upgrades-plugins::writing-upgradeable.adoc#modifying-your-contracts[Writing Upgradeable Contracts].

The namespaced storage pattern used in the Upgradeable package allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments. It also allows changing the inheritance order with no impact on the resulting storage layout, as long as all inherited contracts use namespaced storage.


Modules ROOT Pages Utilities (`docs/modules/ROOT/pages/utilities.adoc`)

= Utilities

The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. For a complete list, check out the xref:api:utils.adoc[API Reference].
Here are some of the more popular ones.

[[cryptography]]
== Cryptography

=== Checking Signatures On-Chain

At a high level, signatures are a set of cryptographic algorithms that allow for a signer to prove himself as the owner of a private key used to authorize a piece of information (generally a transaction or UserOperation). Natively, the EVM supports the Elliptic Curve Digital Signature Algorithm (https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm[ECDSA]) using the secp256k1 curve, however other signature algorithms such as P256 and RSA are supported.

==== Ethereum Signatures (secp256k1)

xref:api:utils/cryptography.adoc#ECDSA[ECDSA] provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#sign[`web3.eth.sign`], and form a 65-byte array (of type bytes in Solidity) arranged the following way: [[v (1)], [r (32)], [s (32)]].

The data signer can be recovered with xref:api:utils/cryptography.adoc#ECDSA-recover-bytes32-bytes-[ECDSA.recover], and its address compared to verify the signature. Most wallets will hash the data to sign and add the prefix \x19Ethereum Signed Message:\n, so when attempting to recover the signer of an Ethereum signed message hash, you'll want to use xref:api:utils/cryptography.adoc#MessageHashUtils-toEthSignedMessageHash-bytes32-[toEthSignedMessageHash].

[source,solidity]

using ECDSA for bytes32;
using MessageHashUtils for bytes32;

function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
return data
.toEthSignedMessageHash()
.recover(signature) == account;

}

WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:utils/cryptography.adoc#MessageHashUtils[MessageHashUtils]'s and xref:api:utils/cryptography.adoc#ECDSA[ECDSA]'s documentation.

==== P256 Signatures (secp256r1)

P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and they are widely available in consumer hardware and software.

These signatures are different from regular Ethereum Signatures (secp256k1) in that they use a different elliptic curve to perform operations but have similar security guarantees.

[source,solidity]

using P256 for bytes32;

function _verify(
bytes32 data,
bytes32 r,
bytes32 s,
bytes32 qx,
bytes32 qy
) internal view returns (bool) {
return data.verify(r, s, qx, qy);

}

By default, the verify function will try calling the https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md[RIP-7212] precompile at address 0x100 and will fallback to an implementation in Solidity if not available. We encourage you to use verifyNative if you know the precompile is available on the chain you're working on and on any other chain on which you intend to use the same bytecode in the future. In case of any doubts regarding the implementation roadmap of the native precompile P256 of potential future target chains, please consider using verifySolidity.

[source,solidity]

using P256 for bytes32;

function _verify(
bytes32 data,
bytes32 r,
bytes32 s,
bytes32 qx,
bytes32 qy
) internal view returns (bool) {
// Will only call the precompile at address(0x100)
return data.verifyNative(r, s, qx, qy);

}

IMPORTANT: The P256 library only allows for s values in the lower order of the curve (i.e. s <= N/2) to prevent malleability. In case your tooling produces signatures in both sides of the curve, consider flipping the s value to keep compatibility.

==== RSA

RSA is a public-key cryptosystem that was popularized by corporate and governmental public key infrastructures (https://en.wikipedia.org/wiki/Public_key_infrastructure[PKIs]) and https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions[DNSSEC].

This cryptosystem consists of using a private key that's the product of 2 large prime numbers. The message is signed by applying a modular exponentiation to its hash (commonly SHA256), where both the exponent and modulus compose the public key of the signer.

RSA signatures are known for being less efficient than elliptic curve signatures given the size of the keys, which are big compared to ECDSA keys with the same security level. Using plain RSA is considered unsafe, this is why the implementation uses the EMSA-PKCS1-v1_5 encoding method from https://datatracker.ietf.org/doc/html/rfc8017[RFC8017] to include padding to the signature.

To verify a signature using RSA, you can leverage the xref:api:utils/cryptography.adoc#RSA[RSA] library that exposes a method for verifying RSA with the PKCS 1.5 standard:

[source,solidity]

using RSA for bytes32;

function _verify(
bytes32 data,
bytes memory signature,
bytes memory e,
bytes memory n
) internal pure returns (bool) {
return data.pkcs1Sha256(signature, e, n);

}

IMPORTANT: Always use keys of at least 2048 bits. Additionally, be aware that PKCS#1 v1.5 allows for replayability due to the possibility of arbitrary optional parameters. To prevent replay attacks, consider including an onchain nonce or unique identifier in the message.

=== Signature Verification

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
  • ERC-7913 signatures from keys that don't have their own Ethereum address

This allows developers to write signature verification code once and have it work across all these different signature types.

==== Basic Signature Verification

For standard signature verification that supports both EOAs and ERC-1271 contracts:

[source,solidity]

using SignatureChecker for address;

function _verifySignature(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
return SignatureChecker.isValidSignatureNow(signer, hash, signature);

}

The library automatically detects whether the signer is an EOA or a contract and uses the appropriate verification method.

==== ERC-1271 Contract Signatures

For smart contract wallets that implement ERC-1271, you can explicitly use:

[source,solidity]

function _verifyContractSignature(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
return SignatureChecker.isValidERC1271SignatureNow(signer, hash, signature);

}

==== ERC-7913 Extended Signatures

ERC-7913 extends signature verification to support keys that don't have their own Ethereum address. This is useful for integrating non-Ethereum cryptographic curves, hardware devices, or other identity systems.

A signer is represented as a bytes object that concatenates a verifier address and a key: verifier || key.

[source,solidity]

function _verifyERC7913Signature(bytes memory signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
return SignatureChecker.isValidSignatureNow(signer, hash, signature);

}

The verification process works as follows:

  • If signer.length < 20: verification fails
  • If signer.length == 20: verification is done using standard signature checking
  • Otherwise: verification is done using an ERC-7913 verifier

==== Batch Verification

For verifying multiple ERC-7913 signatures at once:

[source,solidity]

function _verifyMultipleSignatures(
bytes32 hash,
bytes[] memory signers,
bytes[] memory signatures
) internal view returns (bool) {
return SignatureChecker.areValidSignaturesNow(hash, signers, signatures);

}

This function will reject inputs that contain duplicated signers. Sorting the signers by their keccak256 hash is recommended to minimize the gas cost.

This unified approach allows smart contracts to accept signatures from any supported source without needing to implement different verification logic for each type.

=== Verifying Merkle Proofs

Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g., for airdrops) and other advanced use cases.

TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs.

xref:api:utils/cryptography.adoc#MerkleProof[MerkleProof] provides:

  • xref:api:utils/cryptography.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[verify] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree].

  • xref:api:utils/cryptography.adoc#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---[multiProofVerify] - can prove multiple values are part of a Merkle tree.

For an on-chain Merkle Tree, see the xref:api:utils.adoc#MerkleTree[MerkleTree] library.

[[introspection]]
== Introspection

In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC-165 is a standard that enables runtime interface detection. Contracts provide helpers both for implementing ERC-165 in your contracts and querying other contracts:

  • xref:api:utils.adoc#IERC165[IERC165] — this is the ERC-165 interface that defines xref:api:utils.adoc#IERC165-supportsInterface-bytes4-[supportsInterface]. When implementing ERC-165, you'll conform to this interface.
  • xref:api:utils.adoc#ERC165[ERC165] — inherit this contract if you'd like to support interface detection using a lookup table in contract storage. You can register interfaces using xref:api:utils.adoc#ERC165-_registerInterface-bytes4-[_registerInterface(bytes4)]: check out example usage as part of the ERC-721 implementation.
  • xref:api:utils.adoc#ERC165Checker[ERC165Checker] — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about.
  • include with using ERC165Checker for address;
  • xref:api:utils.adoc#ERC165Checker-_supportsInterface-address-bytes4-[myAddress._supportsInterface(bytes4)]
  • xref:api:utils.adoc#ERC165Checker-_supportsAllInterfaces-address-bytes4---[myAddress._supportsAllInterfaces(bytes4[\])]

[source,solidity]

contract MyContract {
using ERC165Checker for address;

text
bytes4 private InterfaceId_ERC721 = 0x80ac58cd;

/**
 * @dev transfer an ERC-721 token from this contract to someone else
 */
function transferERC721(
    address token,
    address to,
    uint256 tokenId
)
    public
{
    require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN");
    IERC721(token).transferFrom(address(this), to, tokenId);
}

}

[[math]]
== Math

Although Solidity already provides math operators (i.e. +, -, etc.), Contracts includes xref:api:utils.adoc#Math[Math]; a set of utilities for dealing with mathematical operators, with support for extra operations (e.g., xref:api:utils.adoc#Math-average-uint256-uint256-[average]) and xref:api:utils.adoc#SignedMath[SignedMath]; a library specialized in signed math operations.

Include these contracts with using Math for uint256 or using SignedMath for int256 and then use their functions in your code:

[source,solidity]

contract MyContract {
using Math for uint256;
using SignedMath for int256;

text
function tryOperations(uint256 a, uint256 b) internal pure {
    (bool succeededAdd, uint256 resultAdd) = a.tryAdd(b);
    (bool succeededSub, uint256 resultSub) = a.trySub(b);
    (bool succeededMul, uint256 resultMul) = a.tryMul(b);
    (bool succeededDiv, uint256 resultDiv) = a.tryDiv(b);
    // ...
}

function unsignedAverage(int256 a, int256 b) {
    int256 avg = a.average(b);
    // ...
}

}

Easy!

TIP: While working with different data types that might require casting, you can use xref:api:utils.adoc#SafeCast[SafeCast] for type casting with added overflow checks.

[[structures]]
== Structures

Some use cases require more powerful data structures than the arrays and mappings offered natively in Solidity. These contracts provide libraries for enhanced data structure management:

  • xref:api:utils.adoc#BitMaps[BitMaps]: Store packed booleans in storage.
  • xref:api:utils.adoc#Checkpoints[Checkpoints]: Checkpoint values with built-in lookups.
  • xref:api:utils.adoc#DoubleEndedQueue[DoubleEndedQueue]: Store items in a queue with pop() and queue() constant time operations.
  • xref:api:utils.adoc#EnumerableSet[EnumerableSet]: A https://en.wikipedia.org/wiki/Set_(abstract_data_type)[set] with enumeration capabilities.
  • xref:api:utils.adoc#EnumerableMap[EnumerableMap]: A mapping variant with enumeration capabilities.
  • xref:api:utils.adoc#MerkleTree[MerkleTree]: An on-chain https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] with helper functions.
  • xref:api:utils.adoc#Heap.sol[Heap]: A https://en.wikipedia.org/wiki/Binary_heap[binary heap] to store elements with priority defined by a comparator function.

The Enumerable* structures are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support enumeration, which means you can easily query all stored entries both on and off-chain.

=== Building a Merkle Tree

Building an on-chain Merkle Tree allows developers to keep track of the history of roots in a decentralized manner. For these cases, the xref:api:utils.adoc#MerkleTree[MerkleTree] includes a predefined structure with functions to manipulate the tree (e.g. pushing values or resetting the tree).

The Merkle Tree does not keep track of the roots intentionally, so that developers can choose their tracking mechanism. Setting up and using a Merkle Tree in Solidity is as simple as follows:

NOTE: Functions are exposed without access control for demonstration purposes

[source,solidity]

using MerkleTree for MerkleTree.Bytes32PushTree;
MerkleTree.Bytes32PushTree private _tree;

function setup(uint8 _depth, bytes32 _zero) public /* onlyOwner */ {
root = _tree.setup(_depth, _zero);
}

function push(bytes32 leaf) public /* onlyOwner */ {
(uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf);
// Store the new root.

}

The library also supports custom hashing functions, which can be passed as an extra parameter to the xref:api:utils.adoc#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-[push] and xref:api:utils.adoc#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-[setup] functions.

Using custom hashing functions is a sensitive operation. After setup, it requires continuing to use the same hashing function for every new value pushed to the tree to avoid corrupting the tree. For this reason, it's a good practice to keep your hashing function static in your implementation contract as follows:

[source,solidity]

using MerkleTree for MerkleTree.Bytes32PushTree;
MerkleTree.Bytes32PushTree private _tree;

function setup(uint8 _depth, bytes32 _zero) public /* onlyOwner */ {
root = _tree.setup(_depth, _zero, _hashFn);
}

function push(bytes32 leaf) public /* onlyOwner */ {
(uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf, _hashFn);
// Store the new root.
}

function _hashFn(bytes32 a, bytes32 b) internal view returns(bytes32) {
// Custom hash function implementation
// Kept as an internal implementation detail to
// guarantee the same function is always used

}

=== Using a Heap

A https://en.wikipedia.org/wiki/Binary_heap[binary heap] is a data structure that always stores the most important element at its peak and it can be used as a priority queue.

To define what is most important in a heap, these frequently take comparator functions that tell the binary heap whether a value has more relevance than another.

OpenZeppelin Contracts implements a Heap data structure with the properties of a binary heap. The heap uses the xref:api:utils.adoc#Comparators-lt-uint256-uint256-[lt] function by default but allows to customize its comparator.

When using a custom comparator, it's recommended to wrap your function to avoid the possibility of mistakenly using a different comparator function:

[source,solidity]

function pop(Uint256Heap storage self) internal returns (uint256) {
return pop(self, Comparators.gt);
}

function insert(Uint256Heap storage self, uint256 value) internal {
insert(self, value, Comparators.gt);
}

function replace(Uint256Heap storage self, uint256 newValue) internal returns (uint256) {
return replace(self, newValue, Comparators.gt);

}

[[misc]]
== Misc

=== Packing

The storage in the EVM is shaped in chunks of 32 bytes, each of these chunks is known as a slot, and can hold multiple values together as long as these values don't exceed 32 bytes. This property allows for a technique known as packing--placing values together in a single storage slot to reduce the costs associated with reading and writing these values.

Commonly, developers pack values using structs that place values together so they fit better in storage. However, this approach requires loading such struct from either calldata or memory. Although sometimes necessary, it may be useful to pack values in a single slot and treat it as a packed value without involving calldata or memory.

The xref:api:utils.adoc#Packing[Packing] library is a set of utilities for packing values that fit in 32 bytes. The library includes 3 main functionalities:

  • Packing 2 bytesXX values
  • Extracting a packed bytesXX value from a bytesYY
  • Replacing a packed bytesXX value from a bytesYY

With these primitives, one can build custom functions to create custom packed types. For example, suppose you need to pack an address of 20 bytes with a bytes4 selector and an uint64 time period:

[source,solidity]

function _pack(address account, bytes4 selector, uint64 period) external pure returns (bytes32) {
bytes12 subpack = Packing.pack_4_8(selector, bytes8(period));
return Packing.pack_20_12(bytes20(account), subpack);
}

function _unpack(bytes32 pack) external pure returns (address, bytes4, uint64) {
return (
address(Packing.extract_32_20(pack, 0)),
Packing.extract_32_4(pack, 20),
uint64(Packing.extract_32_8(pack, 24))
);

}

=== Storage Slots

Solidity allocates a storage pointer for each variable declared in a contract. However, there are cases when it's required to access storage pointers that can't be derived by using regular Solidity.
For those cases, the xref:api:utils.adoc#StorageSlot[StorageSlot] library allows for manipulating storage slots directly.

[source,solidity]

bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}

function _setImplementation(address newImplementation) internal {
require(newImplementation.code.length > 0);
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;

}

The xref:api:utils.adoc#TransientSlot[TransientSlot] library supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity.

[source,solidity]

bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;

function _getTransientLock() internal view returns (bool) {
return _LOCK_SLOT.asBoolean().tload();
}

function _setTransientLock(bool lock) internal {
_LOCK_SLOT.asBoolean().tstore(lock);

}

WARNING: Manipulating storage slots directly is an advanced practice. Developers MUST make sure that the storage pointer is not colliding with other variables.

One of the most common use cases for writing directly to storage slots is ERC-7201 for namespaced storage, which is guaranteed to not collide with other storage slots derived by Solidity.

Users can leverage this standard using the xref:api:utils.adoc#SlotDerivation[SlotDerivation] library.

[source,solidity]

using SlotDerivation for bytes32;
string private constant _NAMESPACE = ""; // e.g. example.main

function erc7201Pointer() internal view returns (bytes32) {
return _NAMESPACE.erc7201Slot();

}

=== Base64

xref:api:utils.adoc#Base64[Base64] util allows you to transform bytes32 data into its Base64 string representation.

This is especially useful for building URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[ERC-721] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[ERC-1155]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures.

Here is an example to send JSON Metadata through a Base64 Data URI using an ERC-721:

[source,solidity]

include::api:example$utilities/Base64NFT.sol[]

=== Multicall

The Multicall abstract contract comes with a multicall function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails.

Consider this dummy contract:

[source,solidity]

include::api:example$utilities/Multicall.sol[]

This is how to call the `multicall` function using Ethers.js, allowing `foo` and `bar` to be called in a single transaction: [source,javascript]

// scripts/foobar.js

const instance = await ethers.deployContract("Box");

await instance.multicall([
instance.interface.encodeFunctionData("foo"),
instance.interface.encodeFunctionData("bar")

]);

=== Low-level Calls

The xref:api:utils.adoc#LowLevelCall[LowLevelCall] library provides low-level external calls with fixed-size return data handling, protecting against return bombing attacks where callees allocate excessive memory.

The library efficiently handles return data up to 64 bytes, allowing you to ignore it entirely or extract 1-2 bytes32 values:

[source,solidity]

using LowLevelCall for address;

function example(address target, bytes memory data) internal {
bool success;
bytes32 result1;
bytes32 result2;

text
// Ignore return data
success = target.callNoReturn(data);

// Extract single 32-byte value
(success, result1, ) = target.callReturn64Bytes(data);

// Extract two 32-byte values
(success, result1, result2) = target.callReturn64Bytes(data);

}

You can also check return data size before processing:

[source,solidity]

function checkReturnSize(address target, bytes memory data) internal returns (uint256 value, uint256 otherValue) {
(bool success, bytes32 result1, bytes32 result2) = target.callReturn64Bytes(data);

text
if (!success || LowLevelCall.returnDataSize() < 32) {
    return (0, 0);
} else if (LowLevelCall.returnDataSize() < 64) {
    return (uint256(result1), 0);
} else {
    return (uint256(result1), uint256(result2));
}

}

=== Memory

The xref:api:utils.adoc#Memory[Memory] library provides functions for advanced use cases that require granular memory management. A common use case is to avoid unnecessary memory expansion costs when performing repeated operations that allocate memory in a loop. Consider the following example:

[source,solidity]

function processMultipleItems(uint256[] memory items) internal {
for (uint256 i = 0; i < items.length; i++) {
bytes memory tempData = abi.encode(items[i], block.timestamp);
// Process tempData...
}

}

Note that each iteration allocates new memory for tempData, causing the memory to expand continuously. This can be optimized by resetting the memory pointer between iterations:

[source,solidity]

function processMultipleItems(uint256[] memory items) internal {
Memory.Pointer ptr = Memory.getFreeMemoryPointer(); // Cache pointer
for (uint256 i = 0; i < items.length; i++) {
bytes memory tempData = abi.encode(items[i], block.timestamp);
// Process tempData...
Memory.unsafeSetFreeMemoryPointer(ptr); // Reset pointer for reuse
}

}

This way, memory allocated for tempData in each iteration is reused, significantly reducing memory expansion costs when processing many items.

IMPORTANT: Only use these functions after carefully confirming they're necessary. By default, Solidity handles memory safely. Using this library without understanding memory layout and safety may be dangerous. See the https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_memory.html[memory layout] and https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety[memory safety] documentation for details.

=== Historical Block Hashes

xref:api:utils.adoc#Blockhash[Blockhash] provides L2 protocol developers with extended access to historical block hashes beyond Ethereum's native 256-block limit. By leveraging https://eips.ethereum.org/EIPS/eip-2935[EIP-2935]'s history storage contract, the library enables access to block hashes up to 8,191 blocks in the past, making it invaluable for L2 fraud proofs and state verification systems.

The library seamlessly combines native BLOCKHASH opcode access for recent blocks (≤256) with EIP-2935 history storage queries for older blocks (257-8,191). It handles edge cases gracefully by returning zero for future blocks or those beyond the history window, matching the EVM's behavior. The implementation uses gas-efficient assembly for static calls to the history storage contract.

[source,solidity]

contract L1Inbox {
using Blockhash for uint256;

text
function verifyBlockHash(uint256 blockNumber, bytes32 expectedHash) public view returns (bool) {
    return blockNumber.blockHash() == expectedHash;
}

}

IMPORTANT: After EIP-2935 activation, it takes 8,191 blocks to completely fill the history storage. Before that, only block hashes since the fork block will be available.

=== Time

The xref:api:utils.adoc#Time[Time] library provides helpers for manipulating time-related objects in a type-safe manner. It uses uint48 for timepoints and uint32 for durations, helping to reduce gas costs while providing adequate precision.

One of its key features is the Delay type, which represents a duration that can automatically change its value at a specified point in the future while maintaining delay guarantees. For example, when reducing a delay value (e.g., from 7 days to 1 day), the change only takes effect after the difference between the old and new delay (i.e. a 6 days) or a minimum setback period, preventing an attacker who gains admin access from immediately reducing security timeouts and executing sensitive operations. This is particularly useful for governance and security mechanisms where timelock periods need to be enforced.

Consider this example for using and safely updating Delays: [source,solidity]

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {Time} from "contracts/utils/types/Time.sol";

contract MyDelayedContract {
using Time for *;

text
Time.Delay private _delay;

constructor() {
    _delay = Time.toDelay(3 days);
}

function schedule(bytes32 operationId) external {
    // Get the current `_delay` value, respecting any pending delay changes if they've taken effect
    uint32 currentDelay = _delay.get();
    uint48 executionTime = Time.timestamp() + currentDelay;

    // ... schedule the operation at `executionTime`
}

function execute(bytes32 operationId) external {
    uint48 executionTime = getExecutionTime(operationId);
    require(executionTime > 0, "Operation not scheduled");
    require(Time.timestamp() >= executionTime, "Delay not elapsed yet");

    // ... execute the operation
}

// Update the delay with `Time`'s safety mechanism
function updateDelay(uint32 newDelay) external {
    (Time.Delay updatedDelay, uint48 effect) = _delay.withUpdate(
        newDelay,    // The new delay value
        5 days       // Minimum setback if reducing the delay
    );

    _delay = updatedDelay;

    // ... emit events
}

// Get complete delay details including pending changes
function getDelayDetails() external view returns (
    uint32 currentValue, // The current delay value
    uint32 pendingValue, // The pending delay value
    uint48 effectTime    // The timepoint when the pending delay change takes effect
) {
    return _delay.getFull();
}

}

This pattern is used extensively in OpenZeppelin's xref:api:access.adoc#AccessManager[AccessManager] for implementing secure time-based access control. For example, when changing an admin delay:

[source,solidity]

// From AccessManager.sol
function _setTargetAdminDelay(address target, uint32 newDelay) internal virtual {
uint48 effect;
(_targets[target].adminDelay, effect) = _targets[target].adminDelay.withUpdate(
newDelay,
minSetback()
);

text
emit TargetAdminDelayUpdated(target, newDelay, effect);

}


Modules ROOT Pages Wizard (`docs/modules/ROOT/pages/wizard.adoc`)

= Contracts Wizard
:page-notoc:

Not sure where to start? Use the interactive generator below to bootstrap your
contract and learn about the components offered in OpenZeppelin Contracts.

TIP: Place the resulting contract in your contracts or src directory in order to compile it with a tool like Hardhat or Foundry. Consider reading our guide on xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] for more guidance!

++++


++++


--- METRICS ---